loading
Generated 2020-08-26T09:22:35-05:00

All Files ( 73.88% covered at 896.06 hits/line )

103 files in total.
8823 relevant lines, 6518 lines covered and 2305 lines missed. ( 73.88% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/origen/application/configuration.rb 87.50 % 211 80 70 10 316.83
lib/origen/application/deployer.rb 17.39 % 250 138 24 114 0.52
lib/origen/application/environment.rb 63.74 % 186 91 58 33 37.34
lib/origen/application/lsf.rb 36.71 % 168 79 29 50 0.47
lib/origen/application/lsf_manager.rb 18.58 % 668 366 68 298 2.64
lib/origen/application/plugins.rb 85.07 % 128 67 57 10 452.51
lib/origen/application/runner.rb 60.31 % 283 131 79 52 45.31
lib/origen/application/statistics.rb 84.35 % 191 115 97 18 201.62
lib/origen/application/target.rb 68.05 % 395 169 115 54 72.51
lib/origen/application/workspace_manager.rb 36.36 % 151 77 28 49 0.88
lib/origen/bugs/bug.rb 100.00 % 36 20 20 0 3.90
lib/origen/chip_mode.rb 83.56 % 130 73 61 12 62.81
lib/origen/chip_package.rb 23.73 % 461 236 56 180 3.56
lib/origen/code_generators.rb 51.25 % 131 80 41 39 3.05
lib/origen/code_generators/actions.rb 51.38 % 468 218 112 106 12.47
lib/origen/code_generators/base.rb 94.12 % 64 34 32 2 14.32
lib/origen/code_generators/block.rb 76.86 % 203 121 93 28 2.34
lib/origen/code_generators/block_common.rb 100.00 % 100 67 67 0 5.45
lib/origen/code_generators/dut.rb 85.29 % 62 34 29 5 1.56
lib/origen/code_generators/feature.rb 77.27 % 50 22 17 5 0.77
lib/origen/code_generators/klass.rb 77.27 % 41 22 17 5 0.77
lib/origen/code_generators/model.rb 80.77 % 60 26 21 5 1.27
lib/origen/code_generators/module.rb 66.67 % 92 48 32 16 4.10
lib/origen/commands/compile.rb 100.00 % 61 31 31 0 1.10
lib/origen/commands/generate.rb 91.11 % 131 45 41 4 0.98
lib/origen/commands/helpers.rb 40.00 % 18 10 4 6 2.20
lib/origen/commands/program.rb 89.36 % 70 47 42 5 1.85
lib/origen/controller.rb 84.00 % 150 75 63 12 136.36
lib/origen/database.rb 100.00 % 6 4 4 0 2.00
lib/origen/database/key_value_store.rb 74.68 % 146 79 59 20 146.15
lib/origen/database/key_value_stores.rb 56.36 % 112 55 31 24 101.82
lib/origen/errata/hw_erratum.rb 100.00 % 47 22 22 0 2.41
lib/origen/errata/sw_erratum_workaround.rb 100.00 % 39 18 18 0 3.17
lib/origen/features/feature.rb 100.00 % 22 13 13 0 2.85
lib/origen/file_handler.rb 67.08 % 457 243 163 80 25.65
lib/origen/generator/compiler.rb 73.86 % 295 153 113 40 11.61
lib/origen/generator/job.rb 85.83 % 221 127 109 18 93.69
lib/origen/generator/pattern_finder.rb 73.17 % 161 82 60 22 11.51
lib/origen/generator/pattern_iterator.rb 86.67 % 55 30 26 4 57.00
lib/origen/generator/pattern_sequence.rb 95.51 % 255 156 149 7 280.88
lib/origen/generator/pattern_sequencer.rb 96.97 % 118 66 64 2 548.80
lib/origen/generator/pattern_thread.rb 96.00 % 177 100 96 4 1123.93
lib/origen/generator/renderer.rb 68.75 % 115 64 44 20 43.97
lib/origen/generator/stage.rb 92.68 % 89 41 38 3 637.29
lib/origen/location.rb 100.00 % 6 4 4 0 1.00
lib/origen/location/base.rb 86.36 % 116 66 57 9 11.47
lib/origen/location/map.rb 95.56 % 83 45 43 2 4.22
lib/origen/mode.rb 96.88 % 65 32 31 1 125.53
lib/origen/models.rb 100.00 % 6 4 4 0 1.00
lib/origen/models/mux.rb 100.00 % 26 15 15 0 6.47
lib/origen/models/scan_register.rb 100.00 % 74 46 46 0 25.50
lib/origen/netlist/connectable.rb 100.00 % 18 11 11 0 10.82
lib/origen/netlist/list.rb 93.48 % 152 92 86 6 218.57
lib/origen/org_file.rb 28.95 % 125 76 22 54 0.58
lib/origen/org_file/interceptable.rb 68.00 % 40 25 17 8 3534.32
lib/origen/org_file/interceptor.rb 55.10 % 100 49 27 22 32066.76
lib/origen/parameters/live.rb 100.00 % 22 14 14 0 46.93
lib/origen/parameters/missing.rb 100.00 % 28 10 10 0 1.90
lib/origen/parameters/set.rb 98.08 % 192 104 102 2 644.39
lib/origen/pins/function_proxy.rb 94.74 % 44 19 18 1 6.74
lib/origen/pins/ground_pin.rb 100.00 % 6 3 3 0 2.00
lib/origen/pins/other_pin.rb 100.00 % 6 3 3 0 2.00
lib/origen/pins/pin.rb 85.53 % 1177 615 526 89 1873.40
lib/origen/pins/pin_bank.rb 93.75 % 423 224 210 14 3727.37
lib/origen/pins/pin_clock.rb 95.12 % 138 82 78 4 33.37
lib/origen/pins/pin_collection.rb 90.16 % 566 305 275 30 1776.71
lib/origen/pins/pin_common.rb 82.61 % 206 92 76 16 6515.03
lib/origen/pins/power_pin.rb 100.00 % 39 18 18 0 23.94
lib/origen/pins/timing/timeset.rb 90.22 % 169 92 83 9 20.66
lib/origen/pins/timing/wave.rb 86.42 % 147 81 70 11 54.22
lib/origen/pins/virtual_pin.rb 100.00 % 9 5 5 0 2.00
lib/origen/ports/bit_collection.rb 100.00 % 6 3 3 0 2.00
lib/origen/ports/port.rb 88.61 % 134 79 70 9 58.01
lib/origen/ports/port_collection.rb 90.91 % 19 11 10 1 37.55
lib/origen/ports/section.rb 90.63 % 116 64 58 6 132.33
lib/origen/registers/bit.rb 82.27 % 572 282 232 50 3576.11
lib/origen/registers/bit_collection.rb 76.23 % 1049 467 356 111 882.51
lib/origen/registers/container.rb 100.00 % 288 96 96 0 76.15
lib/origen/registers/domain.rb 100.00 % 16 9 9 0 36.00
lib/origen/registers/msb0_delegator.rb 92.00 % 47 25 23 2 34.44
lib/origen/registers/reg.rb 87.50 % 1620 816 714 102 1080.21
lib/origen/registers/reg_collection.rb 100.00 % 24 10 10 0 276.10
lib/origen/revision_control.rb 66.67 % 47 15 10 5 1.33
lib/origen/revision_control/base.rb 58.82 % 266 68 40 28 3.94
lib/origen/revision_control/git.rb 36.12 % 409 227 82 145 2.42
lib/origen/specs/creation_info.rb 100.00 % 39 13 13 0 3.23
lib/origen/specs/doc_resource.rb 35.71 % 139 70 25 45 4.57
lib/origen/specs/documentation.rb 100.00 % 65 24 24 0 6.58
lib/origen/specs/exhibit.rb 100.00 % 54 27 27 0 4.67
lib/origen/specs/mode_select.rb 100.00 % 60 22 22 0 3.64
lib/origen/specs/note.rb 100.00 % 56 20 20 0 8.00
lib/origen/specs/override.rb 100.00 % 22 16 16 0 6.81
lib/origen/specs/power_supply.rb 100.00 % 63 26 26 0 6.77
lib/origen/specs/spec.rb 44.90 % 321 147 66 81 9.03
lib/origen/specs/spec_features.rb 100.00 % 46 18 18 0 5.11
lib/origen/specs/version_history.rb 100.00 % 16 10 10 0 5.00
lib/origen/utility/block_args.rb 81.25 % 93 16 13 3 10.69
lib/origen/utility/collector.rb 95.00 % 108 40 38 2 31.98
lib/origen/utility/diff.rb 86.75 % 162 83 72 11 3594.13
lib/origen/utility/input_capture.rb 10.45 % 121 67 7 60 0.21
lib/origen/value.rb 96.67 % 119 60 58 2 6.25
lib/origen/value/bin_str_val.rb 97.22 % 72 36 35 1 6.92
lib/origen/value/hex_str_val.rb 97.96 % 100 49 48 1 7.53

CoreExt ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Specs ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

SiteConfig ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Chips ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Clocks ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Fuses ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Database ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Tests ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Features ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Users ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Utility ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

RevisionControl ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Value ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Pins ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

CodeGenerators ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Model ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Registers ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

PowerDomains ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Models ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Bugs ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Errata ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Ports ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Generator ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Boot ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Limits ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Location ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

OrgFile ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Commands ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Parameters ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Netlist ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Application ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Ungrouped ( 73.88% covered at 896.06 hits/line )

103 files in total.
8823 relevant lines, 6518 lines covered and 2305 lines missed. ( 73.88% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/origen/application/configuration.rb 87.50 % 211 80 70 10 316.83
lib/origen/application/deployer.rb 17.39 % 250 138 24 114 0.52
lib/origen/application/environment.rb 63.74 % 186 91 58 33 37.34
lib/origen/application/lsf.rb 36.71 % 168 79 29 50 0.47
lib/origen/application/lsf_manager.rb 18.58 % 668 366 68 298 2.64
lib/origen/application/plugins.rb 85.07 % 128 67 57 10 452.51
lib/origen/application/runner.rb 60.31 % 283 131 79 52 45.31
lib/origen/application/statistics.rb 84.35 % 191 115 97 18 201.62
lib/origen/application/target.rb 68.05 % 395 169 115 54 72.51
lib/origen/application/workspace_manager.rb 36.36 % 151 77 28 49 0.88
lib/origen/bugs/bug.rb 100.00 % 36 20 20 0 3.90
lib/origen/chip_mode.rb 83.56 % 130 73 61 12 62.81
lib/origen/chip_package.rb 23.73 % 461 236 56 180 3.56
lib/origen/code_generators.rb 51.25 % 131 80 41 39 3.05
lib/origen/code_generators/actions.rb 51.38 % 468 218 112 106 12.47
lib/origen/code_generators/base.rb 94.12 % 64 34 32 2 14.32
lib/origen/code_generators/block.rb 76.86 % 203 121 93 28 2.34
lib/origen/code_generators/block_common.rb 100.00 % 100 67 67 0 5.45
lib/origen/code_generators/dut.rb 85.29 % 62 34 29 5 1.56
lib/origen/code_generators/feature.rb 77.27 % 50 22 17 5 0.77
lib/origen/code_generators/klass.rb 77.27 % 41 22 17 5 0.77
lib/origen/code_generators/model.rb 80.77 % 60 26 21 5 1.27
lib/origen/code_generators/module.rb 66.67 % 92 48 32 16 4.10
lib/origen/commands/compile.rb 100.00 % 61 31 31 0 1.10
lib/origen/commands/generate.rb 91.11 % 131 45 41 4 0.98
lib/origen/commands/helpers.rb 40.00 % 18 10 4 6 2.20
lib/origen/commands/program.rb 89.36 % 70 47 42 5 1.85
lib/origen/controller.rb 84.00 % 150 75 63 12 136.36
lib/origen/database.rb 100.00 % 6 4 4 0 2.00
lib/origen/database/key_value_store.rb 74.68 % 146 79 59 20 146.15
lib/origen/database/key_value_stores.rb 56.36 % 112 55 31 24 101.82
lib/origen/errata/hw_erratum.rb 100.00 % 47 22 22 0 2.41
lib/origen/errata/sw_erratum_workaround.rb 100.00 % 39 18 18 0 3.17
lib/origen/features/feature.rb 100.00 % 22 13 13 0 2.85
lib/origen/file_handler.rb 67.08 % 457 243 163 80 25.65
lib/origen/generator/compiler.rb 73.86 % 295 153 113 40 11.61
lib/origen/generator/job.rb 85.83 % 221 127 109 18 93.69
lib/origen/generator/pattern_finder.rb 73.17 % 161 82 60 22 11.51
lib/origen/generator/pattern_iterator.rb 86.67 % 55 30 26 4 57.00
lib/origen/generator/pattern_sequence.rb 95.51 % 255 156 149 7 280.88
lib/origen/generator/pattern_sequencer.rb 96.97 % 118 66 64 2 548.80
lib/origen/generator/pattern_thread.rb 96.00 % 177 100 96 4 1123.93
lib/origen/generator/renderer.rb 68.75 % 115 64 44 20 43.97
lib/origen/generator/stage.rb 92.68 % 89 41 38 3 637.29
lib/origen/location.rb 100.00 % 6 4 4 0 1.00
lib/origen/location/base.rb 86.36 % 116 66 57 9 11.47
lib/origen/location/map.rb 95.56 % 83 45 43 2 4.22
lib/origen/mode.rb 96.88 % 65 32 31 1 125.53
lib/origen/models.rb 100.00 % 6 4 4 0 1.00
lib/origen/models/mux.rb 100.00 % 26 15 15 0 6.47
lib/origen/models/scan_register.rb 100.00 % 74 46 46 0 25.50
lib/origen/netlist/connectable.rb 100.00 % 18 11 11 0 10.82
lib/origen/netlist/list.rb 93.48 % 152 92 86 6 218.57
lib/origen/org_file.rb 28.95 % 125 76 22 54 0.58
lib/origen/org_file/interceptable.rb 68.00 % 40 25 17 8 3534.32
lib/origen/org_file/interceptor.rb 55.10 % 100 49 27 22 32066.76
lib/origen/parameters/live.rb 100.00 % 22 14 14 0 46.93
lib/origen/parameters/missing.rb 100.00 % 28 10 10 0 1.90
lib/origen/parameters/set.rb 98.08 % 192 104 102 2 644.39
lib/origen/pins/function_proxy.rb 94.74 % 44 19 18 1 6.74
lib/origen/pins/ground_pin.rb 100.00 % 6 3 3 0 2.00
lib/origen/pins/other_pin.rb 100.00 % 6 3 3 0 2.00
lib/origen/pins/pin.rb 85.53 % 1177 615 526 89 1873.40
lib/origen/pins/pin_bank.rb 93.75 % 423 224 210 14 3727.37
lib/origen/pins/pin_clock.rb 95.12 % 138 82 78 4 33.37
lib/origen/pins/pin_collection.rb 90.16 % 566 305 275 30 1776.71
lib/origen/pins/pin_common.rb 82.61 % 206 92 76 16 6515.03
lib/origen/pins/power_pin.rb 100.00 % 39 18 18 0 23.94
lib/origen/pins/timing/timeset.rb 90.22 % 169 92 83 9 20.66
lib/origen/pins/timing/wave.rb 86.42 % 147 81 70 11 54.22
lib/origen/pins/virtual_pin.rb 100.00 % 9 5 5 0 2.00
lib/origen/ports/bit_collection.rb 100.00 % 6 3 3 0 2.00
lib/origen/ports/port.rb 88.61 % 134 79 70 9 58.01
lib/origen/ports/port_collection.rb 90.91 % 19 11 10 1 37.55
lib/origen/ports/section.rb 90.63 % 116 64 58 6 132.33
lib/origen/registers/bit.rb 82.27 % 572 282 232 50 3576.11
lib/origen/registers/bit_collection.rb 76.23 % 1049 467 356 111 882.51
lib/origen/registers/container.rb 100.00 % 288 96 96 0 76.15
lib/origen/registers/domain.rb 100.00 % 16 9 9 0 36.00
lib/origen/registers/msb0_delegator.rb 92.00 % 47 25 23 2 34.44
lib/origen/registers/reg.rb 87.50 % 1620 816 714 102 1080.21
lib/origen/registers/reg_collection.rb 100.00 % 24 10 10 0 276.10
lib/origen/revision_control.rb 66.67 % 47 15 10 5 1.33
lib/origen/revision_control/base.rb 58.82 % 266 68 40 28 3.94
lib/origen/revision_control/git.rb 36.12 % 409 227 82 145 2.42
lib/origen/specs/creation_info.rb 100.00 % 39 13 13 0 3.23
lib/origen/specs/doc_resource.rb 35.71 % 139 70 25 45 4.57
lib/origen/specs/documentation.rb 100.00 % 65 24 24 0 6.58
lib/origen/specs/exhibit.rb 100.00 % 54 27 27 0 4.67
lib/origen/specs/mode_select.rb 100.00 % 60 22 22 0 3.64
lib/origen/specs/note.rb 100.00 % 56 20 20 0 8.00
lib/origen/specs/override.rb 100.00 % 22 16 16 0 6.81
lib/origen/specs/power_supply.rb 100.00 % 63 26 26 0 6.77
lib/origen/specs/spec.rb 44.90 % 321 147 66 81 9.03
lib/origen/specs/spec_features.rb 100.00 % 46 18 18 0 5.11
lib/origen/specs/version_history.rb 100.00 % 16 10 10 0 5.00
lib/origen/utility/block_args.rb 81.25 % 93 16 13 3 10.69
lib/origen/utility/collector.rb 95.00 % 108 40 38 2 31.98
lib/origen/utility/diff.rb 86.75 % 162 83 72 11 3594.13
lib/origen/utility/input_capture.rb 10.45 % 121 67 7 60 0.21
lib/origen/value.rb 96.67 % 119 60 58 2 6.25
lib/origen/value/bin_str_val.rb 97.22 % 72 36 35 1 6.92
lib/origen/value/hex_str_val.rb 97.96 % 100 49 48 1 7.53

lib/origen/application/configuration.rb

87.5% lines covered

80 relevant lines. 70 lines covered and 10 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. 2 class Configuration
  4. 2 require 'pathname'
  5. # Returns the configuration's application instance
  6. 2 attr_reader :app
  7. 2 attr_accessor :name, :initials, :instructions,
  8. :history_file, :release_directory, :release_email_subject,
  9. :production_targets,
  10. :vault, :output_directory, :reference_directory,
  11. :semantically_version, :log_directory, :pattern_name_translator,
  12. :pattern_directory, :pattern_output_directory, :pattern_prefix, :pattern_postfix,
  13. :pattern_header, :default_lsf_action, :release_instructions, :proceed_with_pattern,
  14. :test_program_output_directory, :erb_trim_mode, :test_program_source_directory,
  15. :test_program_template_directory, :referenced_pattern_list, :program_prefix,
  16. :copy_command, :diff_command, :compile_only_dot_erb_files, :web_directory,
  17. :web_domain,
  18. :strict_errors, :unmanaged_dirs, :unmanaged_files, :remotes,
  19. :external_app_dirs, :lint_test, :shared, :yammer_group, :rc_url, :rc_workflow,
  20. :user_aliases, :release_externally, :gem_name, :disqus_shortname,
  21. :default_plugin, :rc_tag_prepend_v
  22. # Mark any attributes that are likely to depend on properties of the target here,
  23. # this will raise an error if they are ever accessed before the target has been
  24. # instantiated (a concern for Origen core developers only).
  25. #
  26. # These attributes will also receive an enhanced accessor that accepts a block, see
  27. # below for more details on this.
  28. 2 ATTRS_THAT_DEPEND_ON_TARGET = [
  29. :output_directory, :reference_directory, :pattern_postfix, :pattern_prefix,
  30. :pattern_header, :current_plugin_pattern_header, :application_pattern_header, :shared_pattern_header,
  31. :release_directory, :pattern_name_translator, :pattern_directory, :pattern_output_directory,
  32. :proceed_with_pattern, :test_program_output_directory, :test_program_source_directory,
  33. :test_program_template_directory, :referenced_pattern_list, :program_prefix, :web_directory,
  34. :web_domain
  35. ]
  36. # Any attributes that want to accept a block, but not necessarily require the target
  37. # can be added here
  38. 2 ATTRS_THAT_DONT_DEPEND_ON_TARGET = [
  39. :release_instructions, :history_file, :log_directory, :copy_command,
  40. :diff_command, :remotes,
  41. :external_app_dirs
  42. ]
  43. # If a current plugin is present then its value for these attributes will be
  44. # used instead of that from the current application
  45. 2 ATTRS_THAT_CURRENT_PLUGIN_CAN_OVERRIDE = [
  46. :pattern_prefix, :pattern_postfix, :program_prefix, :pattern_header, :pattern_output_directory,
  47. :output_directory, :reference_directory, :test_program_output_directory,
  48. :test_program_template_directory, :referenced_pattern_list
  49. ]
  50. 2 ATTRS_THAT_ARE_SET_TO_A_BLOCK = [
  51. :current_plugin_pattern_header, :application_pattern_header, :shared_pattern_header, #:pattern_footer
  52. ]
  53. 2 def log_deprecations
  54. # unless imports.empty?
  55. # Origen.deprecate "App #{app.name} uses config.imports this will be removed in Origen V3 and a Gemfile/.gemspec should be used instead"
  56. # end
  57. end
  58. 2 def initialize(app)
  59. 16 @app = app
  60. 16 @name = 'Unknown'
  61. 16 @initials = 'NA'
  62. 16 @semantically_version = false
  63. 16 @compile_only_dot_erb_files = true
  64. # Functions used here since Origen.root is not available when this is first instantiated
  65. 27 @output_directory = -> { "#{Origen.root}/output" }
  66. 16 @reference_directory = lambda do
  67. if Origen.config.output_directory.to_s =~ /(\\|\/)output(\\|\/)/
  68. Origen.config.output_directory.to_s.sub(/(\\|\/)output(\\|\/)/, '\1.ref\2')
  69. else
  70. "#{Origen.root}/.ref"
  71. end
  72. end
  73. 16 @release_directory = -> { Origen.root }
  74. 16 @release_email_subject = false
  75. 58 @log_directory = -> { "#{Origen.root}/log" }
  76. 16 @pattern_name_translator = ->(name) { name }
  77. 43 @pattern_directory = -> { "#{Origen.root}/pattern" }
  78. 48 @pattern_output_directory = -> { Origen.app.config.output_directory }
  79. 16 @history_file = -> { "#{Origen.root}/doc/history" }
  80. 16 @default_lsf_action = :clear
  81. 73 @proceed_with_pattern = ->(_name) { true }
  82. 16 @erb_trim_mode = '%'
  83. 18 @referenced_pattern_list = -> { "#{Origen.root}/list/referenced.list" }
  84. 22 @copy_command = -> { Origen.running_on_windows? ? 'copy' : 'cp' }
  85. 16 @diff_command = -> { Origen.running_on_windows? ? 'start winmerge' : 'tkdiff' }
  86. 16 @imports = []
  87. 16 @imports_dev = []
  88. 16 @external_app_dirs = []
  89. 16 @unmanaged_dirs = []
  90. 16 @unmanaged_files = []
  91. 16 @remotes = []
  92. 16 @lint_test = {}
  93. 16 @user_aliases = {}
  94. 16 @rc_tag_prepend_v = true
  95. end
  96. # This defines an enhanced accessor for these attributes that allows them to be assigned
  97. # to an anonymous function to calculate the value based on some property of the target
  98. # objects.
  99. #
  100. # Without this the objects from the target could not be referenced in config/application.rb
  101. # because they don't exist yet, for example this will not work because $dut has not yet
  102. # been instantiated:
  103. # # config/application.rb
  104. #
  105. # config.output_directory = "#{Origen.root}/output/#{$dut.class}"
  106. #
  107. # However this accessor provides a way to do that via the following syntax:
  108. # # config/application.rb
  109. #
  110. # config.output_directory do
  111. # "#{Origen.root}/output/#{$dut.class}"
  112. # end
  113. #
  114. # Or on one line:
  115. # # config/application.rb
  116. #
  117. # config.output_directory { "#{Origen.root}/output/#{$dut.class}" }
  118. #
  119. # Or if you prefer the more explicit:
  120. # # config/application.rb
  121. #
  122. # config.output_directory = ->{ "#{Origen.root}/output/#{$dut.class}" }
  123. 2 def self.add_attribute(name, options = {})
  124. options = {
  125. 54 depend_on_target: true
  126. }.merge(options)
  127. 54 attr_writer name
  128. 54 define_method name do |override = true, &block|
  129. 4373 if block # _given?
  130. 10 instance_variable_set("@#{name}".to_sym, block)
  131. else
  132. 4363 if override && ATTRS_THAT_CURRENT_PLUGIN_CAN_OVERRIDE.include?(name) &&
  133. app.current? && Origen.app.plugins.current
  134. 1053 var = Origen.app.plugins.current.config.send(name, override: false)
  135. end
  136. 4363 var ||= instance_variable_get("@#{name}".to_sym) || options[:default]
  137. 4363 if var.respond_to?('call')
  138. 737 if options[:depend_on_target]
  139. # If an attempt has been made to access this attribute before the target has
  140. # been instantiated raise an error
  141. # Note Origen.app here instead of just app to ensure we are talking to the top level application,
  142. # that is the only one that has a target
  143. 689 unless Origen.app.target_instantiated?
  144. fail "You have attempted to access Origen.config.#{name} before instantiating the target"
  145. end
  146. end
  147. # Some config variables should be left as a block/proc object. If this is one of those, just return the var.
  148. 737 ATTRS_THAT_ARE_SET_TO_A_BLOCK.include?(name) ? var : var.call
  149. else
  150. 3626 var
  151. end
  152. end
  153. end
  154. end
  155. 42 ATTRS_THAT_DEPEND_ON_TARGET.each { |n| add_attribute(n) }
  156. 16 ATTRS_THAT_DONT_DEPEND_ON_TARGET.each { |n| add_attribute(n, depend_on_target: false) }
  157. 2 (ATTRS_THAT_CURRENT_PLUGIN_CAN_OVERRIDE - ATTRS_THAT_DEPEND_ON_TARGET - ATTRS_THAT_DONT_DEPEND_ON_TARGET).each do |name|
  158. if override && ATTRS_THAT_CURRENT_PLUGIN_CAN_OVERRIDE.include?(name) &&
  159. app.current? && Origen.app.plugins.current
  160. var = Origen.app.plugins.current.config.send(name, override: false)
  161. end
  162. var || instance_variable_get("@#{name}".to_sym)
  163. end
  164. 2 def pattern_name_translator(name = nil, &block)
  165. 5 if block
  166. 4 @pattern_name_translator = block
  167. else
  168. 1 @pattern_name_translator.call(name)
  169. end
  170. end
  171. 2 def proceed_with_pattern(name = nil, &block)
  172. 57 if block
  173. @proceed_with_pattern = block
  174. else
  175. 57 @proceed_with_pattern.call(name)
  176. end
  177. end
  178. # Add a new pattern iterator
  179. 2 def pattern_iterator
  180. 6 yield Origen.generator.create_iterator
  181. end
  182. 2 def lsf
  183. 3 app.lsf.configuration
  184. end
  185. # Prevent a new attribute from a future version of Origen from dying before the
  186. # user can be prompted to upgrade
  187. 2 def method_missing(method, *_args, &_block)
  188. method = method.to_s.sub('=', '')
  189. Origen.log.warning "WARNING - unknown configuration attribute in #{app.name}: #{method}"
  190. end
  191. end
  192. end
  193. end

lib/origen/application/deployer.rb

17.39% lines covered

138 relevant lines. 24 lines covered and 114 lines missed.
    
  1. 1 module Origen
  2. 1 class Application
  3. # This class manages deploying an application's website.
  4. #
  5. # The web pages are compiled in the local application workspace and
  6. # deploy consists of copying them to the remote location.
  7. #
  8. # Two directories are maintained in the remote location, one containing the live
  9. # website and another where the new site is copied to during a deploy.
  10. # A symlink is used to indicate which one of the two directories is currently being
  11. # served.
  12. #
  13. # Upon a successful copy the symlink is switched over, thereby providing zero-downtime
  14. # deploys and guaranteeing that the old site will stay up if an error is encountered
  15. # during a deploy.
  16. #
  17. # An alternative method of deploying is also supported by pushing to a Git repository.
  18. 1 class Deployer
  19. 1 require 'fileutils'
  20. 1 attr_writer :directory, :test
  21. # Prepare for deploying, this will raise an error if the current user is found to
  22. # have insufficient permissions to deploy to the target directory
  23. 1 def prepare!(options = {})
  24. if deploy_to_git?
  25. require 'highline/import'
  26. @commit_message = options[:message] || options[:comment] || ask('Enter a deployment commit message: ') do |q|
  27. q.validate = /\w/
  28. q.responses[:not_valid] = "Can't be blank"
  29. end
  30. Origen.log.info "Fetching the website's Git respository..."
  31. git_repo
  32. begin
  33. fail unless git_repo.can_checkin?
  34. rescue
  35. puts "Sorry, but you don't have permission to write to #{Origen.config.web_directory}!"
  36. exit 1
  37. end
  38. else
  39. begin
  40. require_remote_directories
  41. test_file = "#{Origen.config.web_directory}/_test_file.txt"
  42. FileUtils.rm_f(test_file) if File.exist?(test_file)
  43. FileUtils.touch(test_file)
  44. FileUtils.rm_f(test_file)
  45. rescue
  46. puts "Sorry, but you don't have permission to write to #{Origen.config.web_directory}!"
  47. exit 1
  48. end
  49. end
  50. end
  51. 1 def git_sub_dir
  52. 17 if Origen.config.web_directory =~ /\.git\/(.*)$/
  53. 17 Regexp.last_match(1)
  54. end
  55. end
  56. # Returns a RevisionControl::Git object that points to a local copy of the website repo
  57. # which is will build and checkout as required
  58. 1 def git_repo
  59. @git_repo ||= begin
  60. local_path = "#{Origen.config.web_directory.gsub('/', '-').symbolize}"
  61. local_path.gsub!(':', '-') if Origen.os.windows?
  62. local = Pathname.new("#{Origen.app.workspace_manager.imports_directory}/git/#{local_path}")
  63. if git_sub_dir
  64. remote = Origen.config.web_directory.sub("\/#{git_sub_dir}", '')
  65. else
  66. remote = Origen.config.web_directory
  67. end
  68. git = RevisionControl::Git.new(local: local, remote: remote)
  69. if git.initialized?
  70. git.checkout(force: true)
  71. else
  72. git.build(force: true)
  73. end
  74. git
  75. end
  76. end
  77. 1 def test_run?
  78. @test
  79. end
  80. 1 def deploy_to_git?
  81. 17 !!(Origen.config.web_directory =~ /\.git\/?#{git_sub_dir}$/)
  82. end
  83. 1 def require_remote_directories
  84. %w(remote1 remote2).each do |dir|
  85. dir = "#{Origen.config.web_directory}/#{dir}"
  86. unless File.exist?(dir)
  87. FileUtils.mkdir_p dir
  88. end
  89. end
  90. end
  91. 1 def latest_symlink
  92. "#{Origen.config.web_directory}/latest"
  93. end
  94. 1 def offline_remote_directory
  95. if File.exist?(latest_symlink)
  96. if File.readlink(latest_symlink) =~ /remote1/
  97. "#{Origen.config.web_directory}/remote2"
  98. else
  99. "#{Origen.config.web_directory}/remote1"
  100. end
  101. else
  102. "#{Origen.config.web_directory}/remote1"
  103. end
  104. end
  105. 1 def live_remote_directory
  106. if File.exist?(latest_symlink)
  107. if File.readlink(latest_symlink) =~ /remote1/
  108. "#{Origen.config.web_directory}/remote1"
  109. elsif File.readlink(latest_symlink) =~ /remote2/
  110. "#{Origen.config.web_directory}/remote2"
  111. end
  112. end
  113. end
  114. # Deploy a whole web site.
  115. #
  116. # This copies the entire contents of web/output in the application
  117. # directory to the remote server.
  118. 1 def deploy_site
  119. Origen.app.listeners_for(:before_deploy_site).each(&:before_deploy_site)
  120. if deploy_to_git?
  121. dir = git_repo.local.to_s
  122. dir += "/#{git_sub_dir}" if git_sub_dir
  123. # Delete everything so that we don't preserve old files
  124. git_repo.delete_all(git_sub_dir)
  125. FileUtils.mkdir_p(dir) unless File.exist?(dir)
  126. `chmod a+w -R #{Origen.root}/web/output` # Ensure world writable, required?
  127. FileUtils.cp_r "#{Origen.root}/web/output/.", dir
  128. git_repo.checkin git_sub_dir, unmanaged: true, comment: @commit_message
  129. else
  130. # Empty the contents of the remote dir
  131. if File.exist?(offline_remote_directory)
  132. FileUtils.remove_dir(offline_remote_directory, true)
  133. require_remote_directories
  134. end
  135. # Copy the new contents across
  136. `chmod g+w -R #{Origen.root}/web/output` # Ensure group writable
  137. FileUtils.cp_r "#{Origen.root}/web/output/.", offline_remote_directory
  138. `chmod g+w -R #{offline_remote_directory}` # Double ensure group writable
  139. # Make live
  140. create_symlink offline_remote_directory, latest_symlink
  141. index = "#{Origen.config.web_directory}/index.html"
  142. # This symlink allows the site homepage to be accessed from the web root
  143. # directory rather than root directory/latest
  144. unless File.exist?(index)
  145. create_symlink "#{latest_symlink}/index.html", index
  146. end
  147. end
  148. end
  149. 1 def deploy_file(file)
  150. remote_dir = deploy_to_git? ? "#{git_repo.local}/#{git_sub_dir}" : live_remote_directory
  151. if remote_dir
  152. file = Origen.file_handler.clean_path_to(file)
  153. sub_dir = Origen.file_handler.sub_dir_of(file, "#{Origen.root}/templates/web") .to_s
  154. page = file.basename.to_s.sub(/\..*/, '')
  155. # Special case for the main index page
  156. if page == 'index' && sub_dir == '.'
  157. FileUtils.cp "#{Origen.root}/web/output/index.html", remote_dir
  158. file = "#{remote_dir}/index.html"
  159. else
  160. FileUtils.mkdir_p("#{remote_dir}/#{sub_dir}/#{page}")
  161. file = "#{remote_dir}/#{sub_dir}/#{page}/index.html"
  162. FileUtils.cp "#{Origen.root}/web/output/#{sub_dir}/#{page}/index.html", file
  163. end
  164. if deploy_to_git?
  165. git_repo.checkin file, unmanaged: true, comment: @commit_message
  166. end
  167. end
  168. end
  169. 1 def generate_api
  170. dir = "#{Origen.root}/web/output/api"
  171. FileUtils.rm_rf(dir) if File.exist?(dir)
  172. # system("cd #{Origen.root} && rdoc --op api --tab-width 4 --main api_doc/README.txt --title 'Origen (#{Origen.version})' api_doc lib/origen")
  173. if Origen.root == Origen.top
  174. title = "#{Origen.config.name} #{Origen.version}"
  175. else
  176. title = "#{Origen.config.name} #{Origen.app.version}"
  177. end
  178. system("yard doc --output-dir #{Origen.root}/web/output/api --title '#{title}'")
  179. # Yard doesn't have an option to ignore github-style READMEs, so force it here to
  180. # always present the API index on the API homepage for consistency
  181. index = "#{Origen.root}/web/output/api/index.html"
  182. _index = "#{Origen.root}/web/output/api/_index.html"
  183. FileUtils.rm_f(index) if File.exist?(index)
  184. # This removes a prominent link that we are left with to a README file that doesn't work
  185. require 'nokogiri'
  186. doc = Nokogiri::HTML(File.read(_index))
  187. doc.xpath('//h2[contains(text(), "File Listing")]').remove
  188. doc.css('#files').remove
  189. File.open(_index, 'w') { |f| f.write(doc.to_html) }
  190. FileUtils.cp(_index, index)
  191. end
  192. 1 def deploy_archive(id)
  193. dir = live_remote_directory
  194. if dir
  195. id.gsub!('.', '_')
  196. archive_dir = "#{Origen.config.web_directory}/#{id}"
  197. FileUtils.rm_rf(archive_dir) if File.exist?(archive_dir)
  198. FileUtils.mkdir_p archive_dir
  199. FileUtils.cp_r "#{Origen.root}/web/output/.", archive_dir
  200. end
  201. end
  202. 1 def create_symlink(from, to)
  203. `rm -f #{to}` if File.exist?(to)
  204. `ln -s #{from} #{to}` if File.exist?(from)
  205. end
  206. 1 def web_server_dir
  207. "#{Origen.root}/web"
  208. end
  209. 1 def create_web_server_dir
  210. templates_web_dir = 'app/templates/web'
  211. templates_web_dir = 'templates/web' unless File.exist?("#{Origen.root}/#{templates_web_dir}")
  212. if File.exist?("#{Origen.root}/#{templates_web_dir}")
  213. dir = web_server_dir
  214. FileUtils.rm_rf dir if File.exist?(dir)
  215. FileUtils.mkdir_p dir
  216. # Copy the web infrastructure
  217. FileUtils.cp_r Dir.glob("#{Origen.top}/templates/nanoc/*").sort, dir
  218. # Compile the dynamic stuff
  219. Origen.app.runner.launch action: :compile,
  220. files: "#{Origen.top}/templates/nanoc_dynamic",
  221. output: dir
  222. unless Origen.root == Origen.top
  223. # Copy any application overrides if they exist
  224. if File.exist?("#{Origen.root}/templates/nanoc")
  225. FileUtils.cp_r Dir.glob("#{Origen.root}/templates/nanoc/*").sort, dir, remove_destination: true
  226. end
  227. end
  228. @nanoc_dir = dir
  229. end
  230. end
  231. end
  232. end
  233. end

lib/origen/application/environment.rb

63.74% lines covered

91 relevant lines. 58 lines covered and 33 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. # Class to control the environment.
  4. #
  5. # The environment is a Ruby file that is loaded prior to generating every piece of output.
  6. # It is optional, and is loaded before the target, thereby allowing targets to override
  7. # environment settings.
  8. #
  9. # A typical use case for the environment is to setup the test platform, or to set Origen
  10. # to run in debug or simulation mode. It can generally be thought of as a global target.
  11. #
  12. # All environment definition files must live in Origen.root/environment.
  13. #
  14. # An instance of this class is automatically
  15. # instantiated and available globally as Origen.environment.
  16. 2 class Environment
  17. 2 DIR = "#{Origen.root}/environment" # :nodoc:
  18. 2 SAVE_FILE = "#{DIR}/.default" # :nodoc:
  19. 2 DEFAULT_FILE = "#{DIR}/default.rb" # :nodoc:
  20. # Returns the name (the filename) of the current environment
  21. 2 def name
  22. 40 file.basename('.rb').to_s if file
  23. end
  24. # Returns Array of all environments available
  25. 2 def all_environments
  26. 3 envs = []
  27. 3 find('').sort.each do |file|
  28. 6 envs << File.basename(file)
  29. end
  30. 3 envs
  31. end
  32. # Returns true if the environment exists, this can be used to test for the presence
  33. # of an environment before calling one of the other methods to actually apply it.
  34. #
  35. # It will return true if one or more environments are found matching the given name,
  36. # use the unique? method to test if the given name uniquely identifies a valid
  37. # environment.
  38. 2 def exists?(name)
  39. 3 envs = find(name)
  40. 3 envs.size > 0
  41. end
  42. 2 alias_method :exist?, :exists?
  43. # Similar to the exists? method, this will return true only if the given name
  44. # resolves to a single valid environment.
  45. 2 def unique?(name)
  46. 3 envs = find(name)
  47. 3 envs.size == 1
  48. end
  49. # Switch to the supplied environment, name can be a fragment as long as it allows
  50. # a unique environment to be identified.
  51. #
  52. # Calling this method does not affect the default environment setting in the workspace.
  53. 2 def temporary=(name)
  54. 3 envs = find(name)
  55. 3 if envs.size == 0
  56. puts "Sorry no environments were found matching '#{name}'!"
  57. puts 'Here are the available options:'
  58. find('').sort.each do |file|
  59. puts File.basename(file)
  60. end
  61. exit 1
  62. 3 elsif envs.size > 1
  63. puts 'Please try again with one of the following environments:'
  64. envs.sort.each do |file|
  65. puts File.basename(file)
  66. end
  67. exit 1
  68. else
  69. 3 self.file = envs[0]
  70. end
  71. end
  72. 2 alias_method :switch, :temporary=
  73. 2 alias_method :switch_to, :temporary=
  74. # As #temporary= except that the given environment will be set to the workspace default
  75. 2 def default=(name)
  76. 2 if name
  77. self.temporary = name
  78. else
  79. 2 @file = nil
  80. end
  81. 2 save
  82. end
  83. # Prints out the current environment details to the command line
  84. 2 def describe
  85. f = self.file!
  86. puts "Current environment: #{f.basename}"
  87. puts '*' * 70
  88. File.open(f).each do |line|
  89. puts " #{line}"
  90. end
  91. puts '*' * 70
  92. end
  93. # Returns an array of matching environment file paths
  94. 2 def find(name)
  95. 12 if name
  96. 12 name = name.gsub('*', '')
  97. 12 if File.exist?(name)
  98. [name]
  99. 12 elsif File.exist?("#{Origen.root}/environment/#{name}") && name != ''
  100. ["#{Origen.root}/environment/#{name}"]
  101. else
  102. # The below weirdness is to make it recurse into symlinked directories
  103. 12 Dir.glob("#{DIR}/**{,/*/**}/*").sort.uniq.select do |file|
  104. 25 File.basename(file) =~ /#{name}/ && file !~ /.*\.rb.+$/
  105. end
  106. end
  107. else
  108. [nil]
  109. end
  110. end
  111. # Saves the current environment as the workspace default, i.e. the current environment
  112. # will be used by Origen the next time if no other environment is specified
  113. 2 def save # :nodoc:
  114. 2 if @file
  115. File.open(SAVE_FILE, 'w') do |f|
  116. Marshal.dump(file, f)
  117. end
  118. else
  119. 2 forget
  120. end
  121. end
  122. # Load the default file from the workspace default if it exists and return it,
  123. # otherwise returns nil
  124. 2 def default_file
  125. 527 return @default_file if @default_file
  126. 527 if File.exist?(SAVE_FILE)
  127. File.open(SAVE_FILE) do |f|
  128. @default_file = Marshal.load(f)
  129. end
  130. 527 elsif File.exist?(DEFAULT_FILE)
  131. @default_file = Pathname.new(DEFAULT_FILE)
  132. end
  133. 527 @default_file
  134. end
  135. # Returns the environment file (a Pathname object) if it has been defined, otherwise nil
  136. 2 def file # :nodoc:
  137. 531 return @file if @file
  138. 527 if default_file && File.exist?(default_file)
  139. @file = default_file
  140. end
  141. end
  142. # As file except will raise an exception if it hasn't been defined yet
  143. 2 def file! # :nodoc:
  144. unless file
  145. puts 'No environment has been specified!'
  146. puts 'To specify an environment use the -e switch.'
  147. puts 'Look in the environment directory for a list of available environment names.'
  148. exit 1
  149. end
  150. file
  151. end
  152. 2 def file=(path) # :nodoc:
  153. 3 if path
  154. 3 @file = Pathname.new(path)
  155. else
  156. @file = nil
  157. end
  158. end
  159. # Remove the workspace default environment
  160. 2 def forget
  161. 2 File.delete(SAVE_FILE) if File.exist?(SAVE_FILE)
  162. 2 @default_file = nil
  163. end
  164. # Returns true if running with a temporary environment, i.e. if the current
  165. # environment is not the same as the default environment
  166. 2 def temporary?
  167. @file == @default_file
  168. end
  169. end
  170. end
  171. end

lib/origen/application/lsf.rb

36.71% lines covered

79 relevant lines. 29 lines covered and 50 lines missed.
    
  1. 1 module Origen
  2. 1 class Application
  3. # Responsible for handling all submissions to the LSF
  4. 1 class LSF
  5. # The LSF command configuration that will be used for all submissions to
  6. # the LSF. An instance of this class is returned via the configuration
  7. # method and which can be used to modify the LSF behavior on a per-setup
  8. # basis.
  9. 1 class Configuration
  10. # The group parameter, default: nil
  11. 1 attr_accessor :group
  12. # The project parameter, default: 'msg.te'
  13. 1 attr_accessor :project
  14. # The resource parameter, default: 'linux'
  15. 1 attr_accessor :resource
  16. # The queue parameter, default: 'short'
  17. 1 attr_accessor :queue
  18. # When set to true no submissions will be made to LSF and instead the
  19. # command that would have been submitted is printed to the terminal instead
  20. 1 attr_accessor :debug
  21. # Specify the number of cores to use while submitting the job to LSF
  22. # There is a restriction on the number of cores available per queue name
  23. # Below is a table:
  24. # Queue name equivalent Purpose
  25. # interq gui Interactive jobs, like Virtuoso. Max 15 jobs/user
  26. # batchq normal CPU intensive batch jobs, 1 .. 3 threads. Specify # of threads with bsub -n option. Slots/user: ~10% of total batch capacity.
  27. # batchq_mt normal CPU intensive batch jobs, >= 4 threads. Specify # of threads with bsub -n option. Slots: shared with batchq.
  28. # shortq short CPU intensive batch jobs, 1 thread (= 1 core), guaranteed run time 15 minutes. Slots/user: approximately 3x limit in batchq.
  29. # offloadq - Used for offloading cpu intensive batch jobs to cloud, see CloudPortal.
  30. # Do not submit directly into this queue. No real slot limit. Focused on CPU intensive jobs, not using much memory/data.
  31. # distributed normal Run jobs than span multiple hosts.
  32. # - prio High prio queue with low slot count, useful if you don't have slots available in normal queue. See PrioritizingMyJobs.
  33. # - ondemand On-Demand Servers to satisfy urgent and short-term (2 weeks or less) customer compute requirements.
  34. # - wam WAM cron processing
  35. # - grid Low-priority batch jobs (random sim, regressions, etc). Access to all spare CPU cycles.
  36. 1 attr_accessor :cores
  37. 1 def initialize
  38. 1 @group = Origen.site_config.lsf_group
  39. 1 @project = Origen.site_config.lsf_project
  40. 1 @resource = Origen.site_config.lsf_resource
  41. 1 @queue = Origen.site_config.lsf_queue
  42. 1 @debug = Origen.site_config.lsf_debug
  43. 1 @cores = Origen.site_config.lsf_cores
  44. end
  45. end
  46. # Accessor for the global LSF configuration, use this to modify the default
  47. # LSF configuration for a given setup. Typically an alternate configuration would
  48. # be added to the SoC class or the target file, but it can be set from anywhere.
  49. # This method returns an instance of Origen::Application::LSF::Configuration and can
  50. # be used as shown in the example.
  51. #
  52. # Example
  53. # # soc/nevis.rb
  54. #
  55. # Origen::Runner::LSF.configuration do |config|
  56. # # Use "msg.nevis" for the project string when running in Noida
  57. # if %x["domainname"] =~ /nidc/
  58. # config.lsf.project = "msg.nevis"
  59. # end
  60. # end
  61. #
  62. # # Change the default group
  63. # Origen.config.lsf.group = "lam"
  64. 1 def self.configuration
  65. 3 @config ||= Configuration.new
  66. 3 yield @config if block_given?
  67. 3 @config
  68. end
  69. # Returns the configuration for a given LSF instance, which always maps to the
  70. # global configuration instance.
  71. 1 def configuration
  72. 3 self.class.configuration
  73. end
  74. 1 alias_method :config, :configuration
  75. # Submits the given command to the LSF, returns the LSF job ID
  76. 1 def submit(command, options = {})
  77. options = {
  78. dependents: [],
  79. rerunnable: true, # Will rerun automatically if the execution host fails
  80. }.merge(options)
  81. limit_job_submissions do
  82. group = options[:group] || config.group
  83. group = group ? "-G #{group}" : ''
  84. project = options[:project] || config.project
  85. project = project ? "-P #{project}" : ''
  86. resource = options[:resource] || config.resource
  87. resource = resource ? "-R '#{resource}'" : ''
  88. queue = options[:queue] || config.queue
  89. queue = queue ? "-q #{queue}" : ''
  90. cores = options[:cores] || config.cores
  91. cores = cores ? "-n #{cores}" : ''
  92. rerunnable = options[:rerunnable] ? '-r' : ''
  93. if options[:dependents].empty?
  94. dependents = ''
  95. else
  96. dependents = options[:dependents].map { |id| "ended(#{id})" }.join(' && ')
  97. dependents = "-w '#{dependents}'"
  98. end
  99. cmd = "bsub -oo /dev/null #{dependents} #{rerunnable} #{group} #{project} #{resource} #{queue} #{cores} '#{command}'"
  100. if config.debug
  101. puts cmd
  102. '496212' # Return a dummy ID to keep the caller happy
  103. else
  104. output = `#{cmd}`
  105. Origen.log.info output.strip
  106. if output.split("\n").last =~ /Job <(\d+)> is submitted/
  107. Regexp.last_match[1]
  108. else
  109. :error
  110. end
  111. end
  112. end
  113. end
  114. 1 def queuing_job_ids
  115. ids = []
  116. `bjobs 2>&1`.split("\n").each do |line|
  117. if line =~ /^(\d+).*PEND/
  118. ids << Regexp.last_match[1]
  119. end
  120. end
  121. ids
  122. end
  123. 1 def running_job_ids
  124. ids = []
  125. `bjobs 2>&1`.split("\n").each do |line|
  126. if line =~ /^(\d+).*RUN/
  127. ids << Regexp.last_match[1]
  128. end
  129. end
  130. ids
  131. end
  132. 1 def remote_jobs_count
  133. i = 0
  134. `bjobs 2>&1`.split("\n").each do |line|
  135. if line =~ /^(\d+).*(RUN|PEND)/
  136. i += 1
  137. end
  138. end
  139. i
  140. end
  141. # Limits the number of jobs submitted to the LSF at one time, IT will start
  142. # to warn if a single users current job count gets above 500.
  143. # This method prevents that stage from being reached.
  144. 1 def limit_job_submissions
  145. @local_job_count ||= 0
  146. if @local_job_count == 100
  147. while remote_jobs_count > 400
  148. puts 'Waiting for submitted jobs count to fall below limit...'
  149. sleep 5
  150. end
  151. @local_job_count = 0
  152. yield
  153. else
  154. @local_job_count += 1
  155. yield
  156. end
  157. end
  158. end
  159. end
  160. end

lib/origen/application/lsf_manager.rb

18.58% lines covered

366 relevant lines. 68 lines covered and 298 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. # This class is responsible for co-ordinating and monitoring all submissions
  4. # to the LSF. This is in contrast to Origen::Application::LSF which is an API for
  5. # talking to the LSF.
  6. 2 class LSFManager
  7. 2 include Callbacks
  8. # This will be set by the command dispatcher to reflect the currently executing
  9. # command. If LSF jobs are spawned with the same command then any options passed
  10. # to the parent command will automatically be forwarded to the children.
  11. 2 attr_accessor :current_command
  12. 2 def initialize
  13. 2 unless File.exist?(log_file_directory)
  14. FileUtils.mkdir_p(log_file_directory)
  15. end
  16. end
  17. # Picks and returns either the application's LSF instance or the global LSF instance
  18. 2 def lsf
  19. if Origen.running_globally?
  20. Origen.lsf!
  21. else
  22. Origen.app.lsf
  23. end
  24. end
  25. 2 def remote_jobs_file
  26. "#{Origen.root}/.lsf/remote_jobs"
  27. end
  28. # Waits for all jobs to complete, will retry lost jobs (optionally
  29. # failed jobs).
  30. #
  31. # Alternatively supply an :id or an array of :ids to wait only
  32. # for specific job(s) to complete.
  33. 2 def wait_for_completion(options = {})
  34. options = {
  35. max_lost_retries: 10,
  36. max_fail_retries: 0,
  37. poll_duration_in_seconds: 10,
  38. timeout_in_seconds: 3600
  39. }.merge(options)
  40. options[:start_time] ||= Time.now
  41. if Time.now - options[:start_time] < options[:timeout_in_seconds]
  42. # When waiting for ids we will hold by monitoring for the result
  43. # files directly, rather than using the generatic classify routine.
  44. # This is because the most common use case for this is when jobs
  45. # are idling remotely on the LSF and don't want to run into contention
  46. # issues when multiple processes try to classify/save the status.
  47. if options[:id] || options[:ids]
  48. ids = extract_ids([options[:id], options[:ids]].flatten.compact)
  49. if ids.any? { |id| job_running?(id) }
  50. sleep options[:poll_duration_in_seconds]
  51. wait_for_completion(options)
  52. end
  53. else
  54. classify_jobs
  55. print_status(print_insructions: false)
  56. sleep options[:poll_duration_in_seconds]
  57. classify_jobs
  58. resumitted = false
  59. lost_jobs.each do |job|
  60. if job[:submissions] < options[:max_lost_retries] + 1
  61. resubmit_job(job)
  62. resumitted = true
  63. end
  64. end
  65. failed_jobs.each do |job|
  66. if job[:submissions] < options[:max_fail_retries] + 1
  67. resubmit_job(job)
  68. resumitted = true
  69. end
  70. end
  71. classify_jobs
  72. if outstanding_jobs? || resumitted
  73. wait_for_completion(options)
  74. else
  75. print_status
  76. end
  77. end
  78. end
  79. end
  80. 2 def print_status(options = {})
  81. options = {
  82. print_insructions: true
  83. }.merge(options)
  84. if options[:verbose]
  85. print_details(options)
  86. end
  87. Origen.log.info ''
  88. Origen.log.info 'LSF Status'
  89. Origen.log.info '----------'
  90. Origen.log.info "Queuing: #{queuing_jobs.size}"
  91. Origen.log.info "Running: #{running_jobs.size}"
  92. Origen.log.info "Lost: #{lost_jobs.size}"
  93. Origen.log.info ''
  94. Origen.log.info "Passed: #{passed_jobs.size}"
  95. Origen.log.info "Failed: #{failed_jobs.size}"
  96. Origen.log.info ''
  97. if options[:print_insructions]
  98. Origen.log.info 'Common tasks'
  99. Origen.log.info '------------'
  100. if queuing_jobs.size > 0
  101. Origen.log.info 'Queuing'
  102. Origen.log.info ' Show details: origen l -v -t queuing'
  103. Origen.log.info ' Re-submit: origen l -r -t queuing'
  104. end
  105. if running_jobs.size > 0
  106. Origen.log.info 'Running'
  107. Origen.log.info ' Show details: origen l -v -t running'
  108. Origen.log.info ' Re-submit: origen l -r -t running'
  109. end
  110. if lost_jobs.size > 0
  111. Origen.log.info 'Lost'
  112. Origen.log.info ' Show details: origen l -v -t lost'
  113. Origen.log.info ' Re-submit: origen l -r -t lost'
  114. end
  115. if passed_jobs.size > 0
  116. Origen.log.info 'Passed'
  117. Origen.log.info ' Build log: origen l -l'
  118. end
  119. if failed_jobs.size > 0
  120. Origen.log.info 'Failed'
  121. Origen.log.info ' Show details: origen l -v -t failed'
  122. Origen.log.info ' Re-submit: origen l -r -t failed'
  123. end
  124. Origen.log.info ''
  125. Origen.log.info 'Reset the LSF manager (clear all jobs): origen lsf -c -t all'
  126. Origen.log.info ''
  127. end
  128. end
  129. 2 def print_details(options = {})
  130. if options[:id]
  131. Origen.log.info "Job: #{options[:id]}"
  132. Origen.log.info '----' + '-' * options[:id].length
  133. print_details_of(remote_jobs[options[:id]])
  134. else
  135. options[:type] ||= :all
  136. if options[:type] == :all || options[:type] == :queuing
  137. Origen.log.info ''
  138. Origen.log.info 'Queuing'
  139. Origen.log.info '-------'
  140. queuing_jobs.each { |j| print_details_of(j) }
  141. end
  142. if options[:type] == :all || options[:type] == :running
  143. Origen.log.info ''
  144. Origen.log.info 'Running'
  145. Origen.log.info '-------'
  146. running_jobs.each { |j| print_details_of(j) }
  147. end
  148. if options[:type] == :all || options[:type] == :lost
  149. Origen.log.info ''
  150. Origen.log.info 'Lost'
  151. Origen.log.info '----'
  152. lost_jobs.each { |j| print_details_of(j) }
  153. end
  154. if options[:type] == :all || options[:type] == :passed
  155. Origen.log.info ''
  156. Origen.log.info 'Passed'
  157. Origen.log.info '------'
  158. passed_jobs.each { |j| print_details_of(j) }
  159. end
  160. if options[:type] == :all || options[:type] == :failed
  161. Origen.log.info ''
  162. Origen.log.info 'Failed'
  163. Origen.log.info '------'
  164. failed_jobs.each { |j| print_details_of(j) }
  165. end
  166. end
  167. end
  168. 2 def print_details_of(job)
  169. Origen.log.info "#{job[:command]} #{job[:switches]}".gsub(' --exec_remote', '')
  170. Origen.log.info "ID: #{job[:id]}"
  171. Origen.log.info "Submitted: #{time_ago(job[:submitted_at])}"
  172. Origen.log.info ''
  173. end
  174. 2 def time_ago(time)
  175. seconds = (Time.now - time).to_i
  176. if seconds < 60
  177. unit = 'second'
  178. number = seconds
  179. elsif seconds < 3600
  180. unit = 'minute'
  181. number = seconds / 60
  182. elsif seconds < 86_400
  183. unit = 'hour'
  184. number = seconds / 3600
  185. else
  186. unit = 'day'
  187. number = seconds / 86_400
  188. end
  189. "#{number} #{unit}#{number > 1 ? 's' : ''} ago"
  190. end
  191. 2 def outstanding_jobs?
  192. (running_jobs + queuing_jobs).size > 0
  193. end
  194. # Clear jobs from memory
  195. 2 def clear(options)
  196. if options[:type]
  197. if options[:type] == :all
  198. File.delete(remote_jobs_file) if File.exist?(remote_jobs_file)
  199. @remote_jobs = {}
  200. return
  201. else
  202. send("#{options[:type]}_jobs").each do |job|
  203. remote_jobs.delete(job[:id])
  204. end
  205. end
  206. else
  207. remote_jobs.delete(options[:id])
  208. end
  209. end
  210. 2 def clear_all
  211. File.delete(remote_jobs_file) if File.exist?(remote_jobs_file)
  212. if File.exist?(log_file_directory)
  213. FileUtils.rm_rf(log_file_directory)
  214. end
  215. FileUtils.mkdir_p(log_file_directory)
  216. @remote_jobs = {}
  217. clear_caches
  218. end
  219. # Resubmit jobs
  220. 2 def resubmit(options)
  221. if options[:type]
  222. if options[:type] == :all
  223. remote_jobs.each do |_id, job|
  224. resubmit_job(job)
  225. end
  226. else
  227. send("#{options[:type]}_jobs").each do |job|
  228. resubmit_job(job)
  229. end
  230. end
  231. else
  232. resubmit_job(remote_jobs[options[:id]])
  233. end
  234. end
  235. 2 def stats
  236. Origen.app.stats
  237. end
  238. # Build the log file from the completed jobs
  239. 2 def build_log(options = {})
  240. log_method = options[:log_file] ? options[:log_file] : :info
  241. Origen.log.send(log_method, '*' * 70)
  242. completed_jobs.each do |job|
  243. File.open(log_file(job[:id])) do |f|
  244. last_line_blank = false
  245. f.readlines.each do |line|
  246. # Capture and combine the per job stats that look like this:
  247. # Total patterns: 1 1347 0.003674
  248. # New patterns: 0
  249. # Changed patterns: 1
  250. # FAILED patterns: 1
  251. # Total files: 1
  252. # New files: 0
  253. # Changed files: 0
  254. # FAILED files: 1
  255. begin
  256. line.gsub!(/\e\[\d+m/, '') # Remove any coloring
  257. if line =~ /Total patterns:\s+(\d+)/
  258. stats.completed_patterns += Regexp.last_match[1].to_i
  259. elsif line =~ /Total vectors:\s+(\d+)/
  260. stats.total_vectors += Regexp.last_match[1].to_i
  261. elsif line =~ /Total duration:\s+(\d+\.\d+)/
  262. stats.total_duration += Regexp.last_match[1].to_f
  263. elsif line =~ /Total files:\s+(\d+)/
  264. stats.completed_files += Regexp.last_match[1].to_i
  265. elsif line =~ /Changed patterns:\s+(\d+)/
  266. stats.changed_patterns += Regexp.last_match[1].to_i
  267. elsif line =~ /Changed files:\s+(\d+)/
  268. stats.changed_files += Regexp.last_match[1].to_i
  269. elsif line =~ /New patterns:\s+(\d+)/
  270. stats.new_patterns += Regexp.last_match[1].to_i
  271. elsif line =~ /New files:\s+(\d+)/
  272. stats.new_files += Regexp.last_match[1].to_i
  273. elsif line =~ /FAILED patterns:\s+(\d+)/
  274. stats.failed_patterns += Regexp.last_match[1].to_i
  275. elsif line =~ /FAILED files:\s+(\d+)/
  276. stats.failed_files += Regexp.last_match[1].to_i
  277. elsif line =~ /ERROR!/
  278. stats.errors += 1
  279. Origen.log.send :relog, line, options
  280. else
  281. # Compress multiple blank lines
  282. if line =~ /^\s*$/ || line =~ /.*\|\|\s*$/
  283. unless last_line_blank
  284. Origen.log.send(log_method, nil)
  285. last_line_blank = true
  286. end
  287. else
  288. # Screen std origen output
  289. unless line =~ / origen save/ ||
  290. line =~ /Insecure world writable dir/ ||
  291. line =~ /To save all of/
  292. line.strip!
  293. Origen.log.send :relog, line, options
  294. last_line_blank = false
  295. end
  296. end
  297. end
  298. rescue
  299. # Sometimes illegal UTF-8 characters can get into crash dumps, if this
  300. # happens just print the line out rather than die
  301. Origen.log.error line
  302. end
  303. end
  304. end
  305. end
  306. Origen.log.send(log_method, '*' * 70)
  307. stats.print_summary
  308. end
  309. # Returns the logfile that should be used by a given process on the LSF, this
  310. # should be be guaranteed to be unique
  311. 2 def log_file(id)
  312. "#{log_file_directory}/#{log_file_name(id)}"
  313. end
  314. 2 def passed_file(id)
  315. "#{log_file_directory}/#{log_file_name(id)}.passed"
  316. end
  317. 2 def started_file(id)
  318. "#{log_file_directory}/#{log_file_name(id)}.started"
  319. end
  320. 2 def failed_file(id)
  321. "#{log_file_directory}/#{log_file_name(id)}.failed"
  322. end
  323. 2 def log_file_name(id)
  324. # host = `hostname`.strip
  325. "#{id}.txt"
  326. end
  327. 2 def log_file_directory
  328. 2 "#{Origen.root}/.lsf/remote_logs"
  329. end
  330. # Register that the given job ID has completed successfully on the LSF
  331. 2 def job_passed(id)
  332. `touch #{passed_file(id)}`
  333. end
  334. # Register that the given job ID has failed on the LSF
  335. 2 def job_failed(id)
  336. `touch #{failed_file(id)}`
  337. end
  338. 2 def job_started(id)
  339. `touch #{started_file(id)}`
  340. end
  341. 2 def resubmit_job(job)
  342. [log_file(job[:id]), passed_file(job[:id]), failed_file(job[:id]), started_file(job[:id])].each do |file|
  343. FileUtils.rm_f(file) if File.exist?(file)
  344. end
  345. job[:lsf_id] = lsf.submit(command_prefix(job[:id], job[:dependents_ids]) + job[:command] + job[:switches], dependents: job[:dependents_lsf_ids])
  346. job[:status] = nil
  347. job[:completed_at] = nil
  348. job[:submitted_at] = Time.now
  349. job[:submissions] += 1
  350. end
  351. 2 def submit_job(command, options = {})
  352. options = {
  353. lsf_option_string: ''
  354. }.merge(options)
  355. switches = [' ', options[:lsf_option_string], command_options(command)].flatten.compact.join(' ')
  356. id = generate_job_id
  357. dependents_ids = extract_ids([options[:depend], options[:depends], options[:dependent], options[:dependents]].flatten.compact)
  358. dependents_lsf_ids = dependents_ids.map { |dep_id| remote_jobs[dep_id][:lsf_id] }
  359. lsf_id = lsf.submit(command_prefix(id, dependents_ids) + command + switches, dependents: dependents_lsf_ids)
  360. job_attrs = {
  361. id: id,
  362. lsf_id: lsf_id,
  363. command: command,
  364. submitted_at: Time.now,
  365. submissions: 1,
  366. switches: switches,
  367. dependents_ids: dependents_ids,
  368. dependents_lsf_ids: dependents_lsf_ids
  369. }
  370. remote_jobs[id] = job_attrs
  371. end
  372. 2 def extract_ids(jobs_or_ids)
  373. jobs_or_ids.map { |j| j.is_a?(Hash) ? j[:id] : j }
  374. end
  375. 2 def submit_origen_job(cmd, options = {})
  376. if options[:action]
  377. action = options[:action] == :pattern ? ' generate' : " #{options[:action]}"
  378. else
  379. action = ''
  380. end
  381. str = "#{action} #{cmd}".strip
  382. str.sub!('origen ', '') if str =~ /^origen /
  383. # Append the --exec_remote switch to all Origen commands, this allows command
  384. # processing to be altered based on whether it is running locally or
  385. # remotely by testing Origen.running_remotely?
  386. str += ' --exec_remote'
  387. submit_job("origen #{str}", options)
  388. end
  389. 2 def command_prefix(id, dependents)
  390. # define prefix as a blank string if Origen.site_config.lsf_command_prefix is not defined
  391. if Origen.site_config.lsf_command_prefix
  392. prefix = Origen.site_config.lsf_command_prefix
  393. else
  394. prefix = ''
  395. end
  396. prefix += "cd #{Origen.root}; origen l --execute --id #{id} "
  397. unless dependents.empty?
  398. prefix += "--dependents #{dependents.join(',')} "
  399. end
  400. prefix
  401. end
  402. 2 def command_options(command_str)
  403. command_str.sub(/origen\s*/, '') =~ /(\w+)/
  404. command = Regexp.last_match[1]
  405. command = ORIGEN_COMMAND_ALIASES[command] || command
  406. if command == current_command
  407. @command_options
  408. else
  409. ''
  410. end
  411. end
  412. # This will be called by the command dispatcher to record any options that were passed
  413. # in when launching the current command.
  414. # These will be automatically appended if the current command spawns any LSF jobs that
  415. # will invoke the same command.
  416. 2 def command_options=(opts)
  417. # Ensure these options are removed, these are either incompatible with the LSF,
  418. # or will already have been added elsewhere
  419. {
  420. 18 ['-h', '--help'] => false,
  421. ['-w', '--wait'] => false,
  422. ['-d', '--debug'] => false,
  423. ['-c', '--continue'] => false,
  424. '--exec_remote' => false,
  425. ['-t', '--target'] => '*',
  426. ['-e', '--environment'] => '*',
  427. '--id' => '*',
  428. ['-l', '--lsf'] => %w(add clear)
  429. }.each do |names, values|
  430. 162 [names].flatten.each do |name|
  431. 288 ix = opts.index(name)
  432. 288 if ix
  433. 15 opts.delete_at(ix)
  434. 15 [values].flatten.each do |value|
  435. 15 if value && (value == '*' || opts[ix] == value)
  436. 15 opts.delete_at(ix)
  437. end
  438. end
  439. end
  440. end
  441. end
  442. 18 @command_options ||= []
  443. 18 @command_options += opts
  444. end
  445. 2 def add_command_option(*opts)
  446. @command_options ||= []
  447. @command_options += opts
  448. end
  449. 2 def remote_jobs
  450. @remote_jobs ||= restore_remote_jobs || {}
  451. end
  452. 2 def classify_jobs
  453. clear_caches
  454. queuing_job_ids = lsf.queuing_job_ids
  455. running_job_ids = lsf.running_job_ids
  456. remote_jobs.each do |_id, job|
  457. # If the status has already been determined send it straight to the bucket
  458. if job[:status]
  459. send("#{job[:status]}_jobs") << job
  460. else
  461. if job[:lsf_id] == :error
  462. job[:status] = :lost
  463. lost_jobs << job
  464. elsif job_completed?(job[:id])
  465. if job_passed?(job[:id])
  466. job[:status] = :passed
  467. passed_jobs << job
  468. elsif job_failed?(job[:id])
  469. job[:status] = :failed
  470. failed_jobs << job
  471. end
  472. else
  473. if running_job_ids.include?(job[:lsf_id])
  474. running_jobs << job
  475. # Once we have assigned a job as running make sure the job is marked as started
  476. # It can flicker back to queued if the started file takes a long time to arrive
  477. # from the remote host
  478. job_started(job[:lsf_id])
  479. elsif queuing_job_ids.include?(job[:lsf_id])
  480. queuing_jobs << job
  481. elsif job_started?(job[:id])
  482. # There can be considerable latency between the job writing the passed/failed
  483. # file remotely and it showing up on the local machine.
  484. # Give some buffer to that before declaring the file lost.
  485. if job[:completed_at]
  486. if (Time.now - job[:completed_at]) < 60
  487. running_jobs << job
  488. else
  489. lost_jobs << job
  490. end
  491. else
  492. job[:completed_at] = Time.now
  493. running_jobs << job
  494. end
  495. # Give jobs submitted less than a minute ago the benefit of the
  496. # doubt, they may not have shown up in bjobs yet
  497. elsif (Time.now - job[:submitted_at]) < 60
  498. queuing_jobs << job
  499. else
  500. lost_jobs << job
  501. end
  502. end
  503. end
  504. end
  505. end
  506. 2 def clear_caches
  507. @running_jobs = nil
  508. @queuing_jobs = nil
  509. @passed_jobs = nil
  510. @failed_jobs = nil
  511. @lost_jobs = nil
  512. end
  513. 2 def running_jobs
  514. @running_jobs ||= []
  515. end
  516. 2 def queuing_jobs
  517. @queuing_jobs ||= []
  518. end
  519. 2 def completed_jobs
  520. passed_jobs + failed_jobs
  521. end
  522. 2 def passed_jobs
  523. @passed_jobs ||= []
  524. end
  525. # Failed jobs are those that started to produce a log file but did not complete
  526. 2 def failed_jobs
  527. @failed_jobs ||= []
  528. end
  529. # Lost jobs are ones that for whatever reason did not start, or at least get far
  530. # enough to log that they started
  531. 2 def lost_jobs
  532. @lost_jobs ||= []
  533. end
  534. # Returns trus if the given job ID generated a complete file when run on the LSF.
  535. # The complete file is created at the end of a job run and its presence indicates
  536. # that the job ran and got past the generation/compile stage without crashing.
  537. 2 def job_completed?(id)
  538. job_started?(id) &&
  539. (job_passed?(id) || job_failed?(id))
  540. end
  541. 2 def job_running?(id)
  542. !job_completed?(id)
  543. end
  544. 2 def job_started?(id)
  545. File.exist?(started_file(id))
  546. end
  547. 2 def job_passed?(id)
  548. File.exist?(passed_file(id))
  549. end
  550. 2 def job_failed?(id)
  551. File.exist?(failed_file(id))
  552. end
  553. 2 def generate_job_id
  554. "#{Time.now.to_f}".gsub('.', '')
  555. end
  556. 2 def restore_remote_jobs
  557. if File.exist?(remote_jobs_file)
  558. File.open(remote_jobs_file) do |f|
  559. begin
  560. Marshal.load(f)
  561. rescue
  562. nil
  563. end
  564. end
  565. end
  566. end
  567. 2 def on_origen_shutdown(_options = {})
  568. 1 save_remote_jobs if @remote_jobs
  569. end
  570. 2 def save_remote_jobs
  571. File.open(remote_jobs_file, 'w') do |f|
  572. Marshal.dump(@remote_jobs, f)
  573. end
  574. end
  575. 2 def execute_remotely(options = {})
  576. job_started(options[:id])
  577. begin
  578. if options[:dependents]
  579. wait_for_completion(ids: options[:dependents],
  580. poll_duration_in_seconds: 1,
  581. # Don't wait long by the time this runs the LSF
  582. # should have guaranteed the job has run
  583. timeout_in_seconds: 120
  584. )
  585. unless options[:dependents].all? { |id| job_passed?(id) }
  586. File.open(log_file(options[:id]), 'w') do |f|
  587. f.puts "*** ERROR! *** #{options[:cmd].join(' ')} ***"
  588. f.puts 'Dependents failed!'
  589. end
  590. fail 'Dependents failed!'
  591. end
  592. end
  593. if options[:cmd].is_a?(Array)
  594. cmd = options[:cmd].join(' ')
  595. else
  596. cmd = options[:cmd]
  597. end
  598. output = `#{cmd} 2>&1`
  599. File.open(log_file(options[:id]), 'w') do |f|
  600. f.write output
  601. end
  602. if $CHILD_STATUS.success?
  603. job_passed(options[:id])
  604. else
  605. job_failed(options[:id])
  606. end
  607. rescue
  608. job_failed(options[:id])
  609. end
  610. end
  611. end
  612. end
  613. end

lib/origen/application/plugins.rb

85.07% lines covered

67 relevant lines. 57 lines covered and 10 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. # Provides an API for working with the application's plugins
  4. #
  5. # An instance of this class is instantiated as Origen.app.plugins
  6. 2 class Plugins < ::Array
  7. 2 def initialize
  8. 6 top = Origen.app
  9. 6 Origen._applications_lookup[:name].each do |_name, app|
  10. 48 self << app unless app == top
  11. end
  12. end
  13. # Will raise an error if any plugins are currently imported from a path reference
  14. # in the Gemfile
  15. 2 def validate_production_status(force = false)
  16. 394 if Origen.mode.production? || force
  17. 1 if File.exist?("#{Origen.root}/Gemfile")
  18. 1 File.readlines("#{Origen.root}/Gemfile").each do |line|
  19. # http://rubular.com/r/yNGDGB6M2r
  20. 29 if line =~ /^\s*gem\s+(("|')\w+("|')),.*(:path\s*=>|path:)/
  21. fail "The following gem is defined as a path in your Gemfile, but that is not allowed in production: #{Regexp.last_match[1]}"
  22. end
  23. 29 if line =~ /ORIGEN PLUGIN AUTO-GENERATED/
  24. fail 'Fetched gems are currently being used in your Gemfile, but that is not allowed in production!'
  25. end
  26. end
  27. end
  28. end
  29. end
  30. # Returns an array of symbols that represent the names of all plugins
  31. 2 def names
  32. 24 map(&:name)
  33. end
  34. # Returns the current plugin's application instance
  35. 2 def current
  36. 6215 return nil if @temporary == :none
  37. 5060 return nil if @disabled
  38. 5058 name = @temporary || @current ||= begin
  39. 1374 if Origen.app.session.origen_core[:default_plugin]
  40. 2 Origen.app.session.origen_core[:default_plugin]
  41. 1372 elsif Origen.app.config.default_plugin && !Origen.app.session.origen_core[:default_plugin_cleared_by_user]
  42. 1 Origen.app.config.default_plugin
  43. end
  44. end
  45. 10183 find { |p| p.name.to_sym == name } if name
  46. end
  47. 2 def current=(name)
  48. 11 name = name.to_sym if name
  49. 11 if name == :none || name.nil?
  50. 5 @current = nil
  51. 5 Origen.app.session.origen_core[:default_plugin] = nil
  52. 5 Origen.app.session.origen_core[:default_plugin_cleared_by_user] = true
  53. else
  54. 6 Origen.app.session.origen_core[:default_plugin] = name
  55. 6 @current = name
  56. end
  57. end
  58. 2 def temporary=(name)
  59. 84 name = name.to_sym if name
  60. 84 @temporary = name
  61. end
  62. # Temporarily set the current plugin to nil
  63. 2 def disable_current
  64. 2 @disabled = true
  65. 2 if block_given?
  66. 1 yield
  67. 1 @disabled = false
  68. end
  69. end
  70. # Restore the current plugin after an earlier disable
  71. 2 def enable_current
  72. 1 @disabled = false
  73. end
  74. # @deprecated
  75. 2 def default=(name)
  76. 1 Origen.deprecated 'Origen.current_plugin.default= is deprecated, use Origen.app.plugins.current= instead'
  77. 1 self.current = name
  78. end
  79. # @deprecated
  80. 2 def name
  81. 2 Origen.deprecated 'Origen.current_plugin.name is deprecated, use Origen.app.plugins.current.name instead'
  82. 2 current.name if current
  83. end
  84. # @deprecated
  85. 2 def instance
  86. Origen.deprecated 'Origen.current_plugin.instance is deprecated, use Origen.app.plugins.current instead'
  87. current
  88. end
  89. # @deprecated
  90. 2 def default
  91. Origen.deprecated 'Origen.current_plugin.default is deprecated, use Origen.app.plugins.current instead'
  92. current
  93. end
  94. 2 def shared_commands
  95. [Origen.app, self].flatten.map do |plugin|
  96. shared = plugin.config.shared || {}
  97. if shared[:command_launcher]
  98. "#{plugin.root}/#{shared[:command_launcher]}"
  99. end
  100. end.compact
  101. end
  102. # Return the plugin name if the path specified is from that plugin
  103. 2 def plugin_name_from_path(path)
  104. 29 path = Pathname.new(path).expand_path.cleanpath
  105. 29 each do |plugin|
  106. 187 if path.to_s =~ /^#{plugin.root}/
  107. 4 return plugin.name
  108. end
  109. end
  110. nil
  111. end
  112. 2 alias_method :path_within_a_plugin, :plugin_name_from_path
  113. end
  114. end
  115. end

lib/origen/application/runner.rb

60.31% lines covered

131 relevant lines. 79 lines covered and 52 lines missed.
    
  1. 2 require 'fileutils'
  2. 2 module Origen
  3. 2 class Application
  4. 2 autoload :Statistics, 'origen/application/statistics'
  5. # The Runner is responsible for co-ordinating all compile and generate
  6. # requests from the command line
  7. 2 class Runner
  8. 2 attr_accessor :options
  9. # Launch Origen, any command which generates an output file should launch from here
  10. # as it gives a common point for listeners to hook in and to establish output
  11. # directories and so on.
  12. #
  13. # Originally this method was called generate but that is now deprecated in favour
  14. # of the more generic 'launch' as the Origen feature set has expanded.
  15. 2 def launch(options = {})
  16. 18 Origen.file_handler.preserve_state do
  17. # Clean up the input from legacy code
  18. 18 options[:action] = extract_action(options)
  19. 18 options[:files] = extract_files(options)
  20. 18 @options = options
  21. 18 prepare_and_validate_workspace(options)
  22. 18 if options[:lsf]
  23. record_invocation(options) do
  24. prepare_for_lsf
  25. Origen.app.listeners_for(:before_lsf_submission).each(&:before_lsf_submission)
  26. batch = []
  27. expand_lists_and_directories(options[:files], options).each do |file|
  28. if options[:batch]
  29. # Batch jobs into groups of 10
  30. batch << file
  31. if batch.size == options[:batch]
  32. Origen.app.lsf_manager.submit_origen_job(batch.join(' '), options)
  33. batch = []
  34. end
  35. else
  36. Origen.app.lsf_manager.submit_origen_job(file, options)
  37. end
  38. end
  39. if options[:batch]
  40. Origen.app.lsf_manager.submit_origen_job(batch.join(' '), options) unless batch.empty?
  41. end
  42. end
  43. Origen.log.info ''
  44. Origen.log.info 'Monitor status of remote jobs via:'
  45. Origen.log.info ' origen l'
  46. else
  47. 18 unless tester && tester.try(:sim?)
  48. 18 Origen.log.info '*' * 70 unless options[:quiet]
  49. end
  50. 18 Origen.app.listeners_for(:before_generate).each do |listener|
  51. if listener.class.instance_method(:before_generate).arity == 0
  52. listener.before_generate
  53. else
  54. listener.before_generate(options)
  55. end
  56. end
  57. 18 if Origen.running_remotely?
  58. Origen.app.listeners_for(:before_generate_remote).each do |listener|
  59. if listener.class.instance_method(:before_generate_remote).arity == 0
  60. listener.before_generate_remote
  61. else
  62. listener.before_generate_remote(options)
  63. end
  64. end
  65. else
  66. 18 Origen.app.listeners_for(:before_generate_local).each do |listener|
  67. if listener.class.instance_method(:before_generate_local).arity == 0
  68. listener.before_generate_local
  69. else
  70. listener.before_generate_local(options)
  71. end
  72. end
  73. end
  74. 18 record_invocation(options) do
  75. 18 case options[:action]
  76. when :forecast_test_time
  77. Origen.time.forecast_test_time(options)
  78. else
  79. 18 if options[:action] == :program
  80. 2 Origen.generator.generate_program(expand_lists_and_directories(options[:files], options), options)
  81. 2 Origen.app.listeners_for(:program_generated).each(&:program_generated)
  82. else
  83. 16 temporary_plugin_from_options = options[:current_plugin]
  84. 16 if options[:action] == :pattern && options[:sequence]
  85. 1 patterns = expand_lists_and_directories(options[:files], options.merge(preserve_duplicates: true))
  86. 1 Origen.generator.generate_pattern(patterns, options)
  87. 1 Origen.app.plugins.temporary = nil if temporary_plugin_from_options
  88. else
  89. 15 expand_lists_and_directories(options[:files], options).each do |file|
  90. 34 if temporary_plugin_from_options
  91. 32 Origen.app.plugins.temporary = temporary_plugin_from_options
  92. end
  93. 34 case options[:action]
  94. when :compile
  95. 7 Origen.generator.compile_file_or_directory(file, options)
  96. when :merge
  97. Origen.generator.merge_file_or_directory(file, options)
  98. when :import_test_time
  99. Origen.time.import_test_time(file, options)
  100. when :import_test_flow
  101. Origen.time.import_test_flow(file, options)
  102. else
  103. 27 Origen.generator.generate_pattern(file, options)
  104. end
  105. 34 Origen.app.plugins.temporary = nil if temporary_plugin_from_options
  106. end
  107. end
  108. end
  109. end
  110. end
  111. 18 unless options[:quiet] || (tester && tester.try(:sim?))
  112. 18 Origen.log.info '*' * 70
  113. 18 stats.print_summary unless options[:action] == :merge
  114. end
  115. end
  116. end
  117. end
  118. 2 alias_method :generate, :launch
  119. 2 def prepare_and_validate_workspace(options = {})
  120. 164 confirm_production_ready(options)
  121. 164 prepare_directories(options)
  122. end
  123. # Post an invocation to the Origen server for usage statistics tracking.
  124. #
  125. # Posting an invocation was found to add ~0.5s to all command times,
  126. # so here we run it in a separate thread to try and hide it behind
  127. # the user's command.
  128. #
  129. # @api private
  130. 2 def record_invocation(options)
  131. # record_invocation = false
  132. # begin
  133. # # Only record user invocations at this time, also bypass windows since it seems
  134. # # that threads can't be trusted not to block
  135. # unless Origen.running_remotely? # || Origen.running_on_windows?
  136. # record_invocation = Thread.new do
  137. # Origen.client.record_invocation(options[:action]) if options[:action]
  138. # end
  139. # end
  140. # rescue
  141. # # Don't allow this to kill an origen command
  142. # end
  143. 18 yield
  144. # begin
  145. # unless Origen.running_remotely?
  146. # # Wait for a server response, ideally would like to not wait here, but it seems if not
  147. # # then invocation postings can be dropped, especially on windows
  148. # Origen.profile 'waiting for recording invocation' do
  149. # record_invocation.value
  150. # end
  151. # end
  152. # rescue
  153. # # Don't allow this to kill an origen command
  154. # end
  155. end
  156. # The action to take should be set by the action option, but legacy code will pass
  157. # things like :compile => true, the extract_action method handles the old code
  158. 2 def extract_action(options)
  159. 18 return options[:action] if options[:action]
  160. 14 if options[:compile]
  161. 6 :compile
  162. 8 elsif options[:program]
  163. :program
  164. 8 elsif options[:job_type] == :merge
  165. :merge
  166. else
  167. 8 :pattern
  168. end
  169. end
  170. # Legacy file references can be input via :pattern, :patterns, etc. this
  171. # cleans it up and forces them all to be in an array assigned to options[:files]
  172. 2 def extract_files(options)
  173. 18 files = [options[:pattern]] + [options[:patterns]] + [options[:file]] + [options[:files]]
  174. 18 files.flatten!
  175. 18 files.compact!
  176. 18 files
  177. end
  178. 2 def shutdown
  179. 2 if Origen.app.stats.failed_files > 0 ||
  180. Origen.app.stats.failed_patterns > 0
  181. exit 1
  182. end
  183. end
  184. # Expands any list references in the supplied pattern array and
  185. # returns an array of pattern names. No guarantee is made to
  186. # whether the pattern names are valid at this stage.
  187. # Any duplicates will be removed.
  188. 2 def expand_lists_and_directories(files, options = {})
  189. 18 Origen.file_handler.expand_list(files, options)
  190. end
  191. 2 def statistics
  192. 2735 @statistics ||= Statistics.new(options)
  193. end
  194. 2 alias_method :stats, :statistics
  195. 2 def prepare_for_lsf
  196. if options[:lsf]
  197. # Build an options string for saving with the LSF job that represents this runtime environment
  198. str = "-t #{Origen.target.file.basename}"
  199. if Origen.environment.file
  200. str += " --environment #{Origen.environment.file.basename}"
  201. end
  202. if options[:output]
  203. str += " -o #{options[:output]}"
  204. end
  205. if options[:reference]
  206. str += " -r #{options[:reference]}"
  207. end
  208. options[:lsf_option_string] = str
  209. # Clear the LSF manager job list if specifically requested or if that is the default action and
  210. # no specific action has been requested
  211. if options[:lsf_action]
  212. if options[:lsf_action] == :clear
  213. Origen.app.lsf_manager.clear_all
  214. end
  215. elsif Origen.config.default_lsf_action == :clear
  216. Origen.app.lsf_manager.clear_all
  217. end
  218. end
  219. end
  220. 2 def prepare_directories(options = {})
  221. # When running remotely on the LSF never create directories to
  222. # prevent race conditions as multiple processes run concurrently,
  223. # instead assume they were already created by the runner who
  224. # submitted the job.
  225. 164 Origen.file_handler.set_output_directory(options.merge(create: Origen.running_locally?))
  226. 164 Origen.file_handler.set_reference_directory(options.merge(create: Origen.running_locally?))
  227. 164 unless Origen.running_globally?
  228. 164 tmp = "#{Origen.root}/tmp"
  229. 164 FileUtils.mkdir(tmp) unless File.exist?(tmp)
  230. 164 if Origen.running_locally?
  231. 164 mkdir Origen::Log.log_file_directory
  232. 164 mkdir "#{Origen.root}/.lsf"
  233. end
  234. 164 if options[:lsf]
  235. mkdir Origen.app.lsf_manager.log_file_directory
  236. end
  237. end
  238. end
  239. # Make the given directory if it doesn't exist, must be a full path
  240. 2 def mkdir(dir)
  241. 328 unless File.exist?(dir)
  242. FileUtils.mkdir_p(dir)
  243. end
  244. end
  245. 2 def confirm_production_ready(_options = {})
  246. # The caller would have already verified the status before submission
  247. 164 if Origen.running_locally?
  248. 164 if Origen.mode.production? && Origen.app.rc
  249. unless Origen.app.rc.local_modifications.empty?
  250. puts <<-EOT
  251. Your workspace is running in production mode and it has local modifications which are preventing
  252. the requested action, run the following command to see what files have been modified:
  253. origen rc mods
  254. If you are currently developing this application and are not ready to check everything in yet,
  255. then run the following command to switch your workspace to debug/development mode:
  256. origen m debug
  257. EOT
  258. exit 1
  259. end
  260. end
  261. end
  262. end
  263. end
  264. end
  265. end

lib/origen/application/statistics.rb

84.35% lines covered

115 relevant lines. 97 lines covered and 18 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. # Responsible for keeping track of all stats collected during a run
  4. 2 class Statistics
  5. 2 attr_accessor :completed_files, :failed_files, :missing_files,
  6. :new_files, :changed_files
  7. 2 attr_accessor :completed_patterns, :failed_patterns, :missing_patterns,
  8. :new_patterns, :changed_patterns
  9. 2 attr_accessor :total_vectors, :total_cycles, :total_duration, :errors
  10. 2 class Pattern
  11. 2 attr_accessor :vectors, :cycles, :duration
  12. 2 def initialize
  13. 88 @vectors = 0
  14. 88 @cycles = 0
  15. 88 @duration = 0
  16. end
  17. end
  18. 2 def initialize(options)
  19. 2 @options = options
  20. 2 @patterns = {}
  21. 2 reset_global_stats
  22. end
  23. 2 def reset_global_stats
  24. 2 @completed_files = 0
  25. 2 @failed_files = 0
  26. 2 @missing_files = 0
  27. 2 @new_files = 0
  28. 2 @changed_files = 0
  29. 2 @completed_patterns = 0
  30. 2 @failed_patterns = 0
  31. 2 @missing_patterns = 0
  32. 2 @new_patterns = 0
  33. 2 @changed_patterns = 0
  34. 2 @total_vectors = 0
  35. 2 @total_cycles = 0
  36. 2 @total_duration = 0
  37. 2 @errors = 0
  38. end
  39. 2 def reset_pattern_stats
  40. end
  41. 2 def print_summary
  42. 18 method = clean_run? ? :success : :info
  43. 18 if @completed_patterns > 0 || @failed_patterns > 0
  44. 15 Origen.log.send method, "Total patterns: #{@completed_patterns}"
  45. 15 Origen.log.send method, "Total vectors: #{@total_vectors}"
  46. 15 Origen.log.send method, 'Total duration: %.6f' % @total_duration
  47. 15 Origen.log.send method, "New patterns: #{@new_patterns}"
  48. 15 if @changed_patterns > 0
  49. Origen.log.warn "Changed patterns: #{@changed_patterns}"
  50. else
  51. 15 Origen.log.send method, "Changed patterns: #{@changed_patterns}"
  52. end
  53. 15 Origen.log.error "FAILED patterns: #{@failed_patterns}" if @failed_patterns > 0
  54. 15 Origen.log.info
  55. end
  56. 18 if @completed_files > 0 || @failed_files > 0
  57. 18 Origen.log.send method, "Total files: #{@completed_files}"
  58. 18 Origen.log.send method, "New files: #{@new_files}"
  59. 18 Origen.log.send method, "Changed files: #{@changed_files}"
  60. 18 Origen.log.error "FAILED files: #{@failed_files}" if @failed_files > 0
  61. 18 Origen.log.info
  62. end
  63. 18 if @errors > 0
  64. Origen.log.error "ERRORS: #{@errors}"
  65. end
  66. 18 if @changed_files > 0 || @changed_patterns > 0
  67. changes = true
  68. Origen.log.info 'To accept all of these changes run:'
  69. Origen.log.info ' origen save changed'
  70. end
  71. 18 if @new_files > 0 || @new_patterns > 0
  72. 3 news = true
  73. 3 Origen.log.info 'To save all of these new files as the reference version run:'
  74. 3 Origen.log.info ' origen save new'
  75. end
  76. 18 if changes && news
  77. Origen.log.info 'To save both new and changed files run:'
  78. Origen.log.info ' origen save all'
  79. end
  80. 18 Origen.log.info '**********************************************************************'
  81. end
  82. 2 def summary_text
  83. <<-END
  84. Total patterns: #{@completed_patterns}
  85. New patterns: #{@new_patterns}
  86. Changed patterns: #{@changed_patterns}
  87. FAILED patterns: #{@failed_patterns}
  88. Total files: #{@completed_files}
  89. New files: #{@new_files}
  90. Changed files: #{@changed_files}
  91. FAILED files: #{@failed_files}
  92. ERRORS: #{@errors}
  93. END
  94. end
  95. 2 def clean_run?
  96. 18 @changed_files == 0 && @changed_patterns == 0 &&
  97. @new_files == 0 && @new_patterns == 0 &&
  98. @failed_files == 0 && @failed_patterns == 0 &&
  99. @errors == 0
  100. end
  101. 2 def record_failed_pattern
  102. @failed_patterns += 1
  103. end
  104. 2 def record_missing_pattern
  105. @missing_patterns += 1
  106. end
  107. 2 def add_vector(x = 1)
  108. 2325 current_pattern.vectors += x
  109. end
  110. 2 def add_cycle(x = 1)
  111. 2325 current_pattern.cycles += x
  112. end
  113. 2 def add_time_in_ns(x)
  114. 2325 current_pattern.duration += x
  115. end
  116. 2 def collect_for_pattern(key)
  117. 88 @pattern_key = key
  118. 88 yield
  119. 88 @pattern_key = nil
  120. end
  121. 2 def current_pattern
  122. 6975 pattern(@pattern_key)
  123. end
  124. 2 def pattern(key)
  125. 7421 @patterns[key] ||= Pattern.new
  126. end
  127. 2 def number_of_vectors_for(key)
  128. 176 pattern(key).vectors
  129. end
  130. 2 def number_of_cycles_for(key)
  131. 88 pattern(key).vectors
  132. end
  133. 2 def execution_time_for(key)
  134. 182 pattern(key).duration.to_f / 1_000_000_000
  135. end
  136. 2 def record_pattern_completion(key)
  137. 88 @completed_patterns += 1
  138. 88 @total_vectors += number_of_vectors_for(key)
  139. 88 @total_cycles += number_of_cycles_for(key)
  140. 88 @total_duration += execution_time_for(key)
  141. end
  142. 2 def report_pass
  143. 2 Origen.log.success ''
  144. 2 Origen.log.success ' PPPPP AA SSSS SSSS'
  145. 2 Origen.log.success ' PP PP AAAA SS SS SS SS'
  146. 2 Origen.log.success ' PPPPP AA AA SS SS'
  147. 2 Origen.log.success ' PP AAAAAAAA SS SS'
  148. 2 Origen.log.success ' PP AA AA SS SS SS SS'
  149. 2 Origen.log.success ' PP AA AA SSSS SSSS'
  150. 2 Origen.log.success ''
  151. end
  152. 2 def report_fail
  153. Origen.log.error ''
  154. Origen.log.error ' FFFFFF AA II LL'
  155. Origen.log.error ' FF AAAA II LL'
  156. Origen.log.error ' FFFFF AA AA II LL'
  157. Origen.log.error ' FF AAAAAAAA II LL'
  158. Origen.log.error ' FF AA AA II LL'
  159. Origen.log.error ' FF AA AA II LLLLLL'
  160. Origen.log.error ''
  161. end
  162. end
  163. end
  164. end

lib/origen/application/target.rb

68.05% lines covered

169 relevant lines. 115 lines covered and 54 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. # Class to handle the target.
  4. #
  5. # The target is a Ruby file that is run prior to generating each pattern, and
  6. # it should be used to instantiate the top-level models used by the application.
  7. # It can also be used to override and settings within these classes after they
  8. # have been instantiated and before they are run.
  9. #
  10. # All target files must live in Origen.root/target.
  11. #
  12. # An instance of this class is automatically instantiated and available globally
  13. # as Origen.app.target
  14. 2 class Target
  15. 2 DIR = "#{Origen.root}/target" # :nodoc:
  16. 2 SAVE_FILE = "#{DIR}/.default" # :nodoc:
  17. 2 DEFAULT_FILE = "#{DIR}/default.rb" # :nodoc:
  18. # Not a clean unload, but allows objects to be re-instantiated for testing
  19. # @api private
  20. 2 def unload!
  21. Origen.app.unload_target!
  22. end
  23. # Implement a target loop based on the supplied options.
  24. # The options can contain the keys :target or :targets or neither.
  25. #
  26. # In the neither case the loop will run once for the current workspace
  27. # default target.
  28. #
  29. # In the case where one of the keys is present the loop will run for each
  30. # target and the options will be passed into the block with the key :target
  31. # set to the current target.
  32. 2 def loop(options = {})
  33. options = {
  34. set_target: true,
  35. force_debug: false, # Set true to force debug mode for all targets
  36. }.merge(options)
  37. targets = [options.delete(:target), options.delete(:targets)].flatten.compact.uniq
  38. targets = [file!.basename.to_s] if targets.empty?
  39. set = options.delete(:set_target)
  40. targets.each do |target|
  41. Origen.load_target(target, options) if set
  42. options[:target] = target
  43. yield options
  44. end
  45. end
  46. # Use this to implement a loop for each production target, it will automatically
  47. # load each target before yielding to the block.
  48. #
  49. # The production targets are defined by the production_targets configuration
  50. # option.
  51. # === Example
  52. # Origen.app.target.each_production do
  53. # Run something within the context of each target
  54. # end
  55. 2 def each_production(options = {})
  56. options = {
  57. force_debug: false, # Set true to force debug mode for all targets
  58. }.merge(options)
  59. prod_targets.each do |moo, targets|
  60. [targets].flatten.each do |target|
  61. self.temporary = target
  62. Origen.app.load_target!(options)
  63. yield moo
  64. end
  65. end
  66. end
  67. # As each_production except it only yields unique targets. i.e. if you have two
  68. # MOOs that use the same target file defined in the production_targets then this
  69. # method will only yield once.
  70. #
  71. # An array of MOOs that use each target is returned each time.
  72. # === Example
  73. # Origen.app.target.each_unique_production do |moos|
  74. # Run something within the context of each unique target
  75. # end
  76. 2 def each_unique_production(options = {})
  77. options = {
  78. force_debug: false, # Set true to force debug mode for all targets
  79. }.merge(options)
  80. targets = {}
  81. prod_targets.each do |moo, moos_targets|
  82. [moos_targets].flatten.each do |target|
  83. if targets[target]
  84. targets[target] << moo
  85. else
  86. targets[target] = [moo]
  87. end
  88. end
  89. end
  90. targets.each do |target, moos|
  91. self.temporary = target
  92. Origen.app.load_target!(options)
  93. yield moos
  94. end
  95. end
  96. # If the production_targets moo number mapping inclues the current target then
  97. # the MOO number will be returned, otherwise nil
  98. 2 def moo
  99. 3 prod_targets.each do |moo, targets|
  100. 6 [targets].flatten.each do |target|
  101. 6 return moo if File.basename(target, '.rb').to_s == file.basename('.rb').to_s
  102. end
  103. end
  104. nil
  105. end
  106. # Returns the name (the filename) of the current target
  107. 2 def name
  108. 442 file.basename('.rb').to_s if file
  109. end
  110. # Load the target, calling this will re-instantiate all top-level objects
  111. # defined there.
  112. 2 def load!(options = {})
  113. options = {
  114. 89 force_debug: false, # Set true to force debug mode for all targets
  115. }.merge(options)
  116. 89 Origen.app.load_target!(options)
  117. end
  118. # Returns Array of all targets available
  119. 2 def all_targets
  120. 6 targets = []
  121. 6 find('').sort.each do |file|
  122. 102 targets << File.basename(file)
  123. end
  124. 6 targets # return
  125. end
  126. # Returns an array containing all current production targets
  127. 2 def production_targets
  128. prod_targets.map { |_moo, targets| targets }.uniq
  129. end
  130. # Returns true if the target exists, this can be used to test for the presence
  131. # of a target before calling one of the other methods to actually apply it.
  132. #
  133. # It will return true if one or more targets are found matching the given name,
  134. # use the unique? method to test if the given name uniquely identifies a valid
  135. # target.
  136. 2 def exists?(name)
  137. 7 tgts = resolve_mapping(name)
  138. 7 targets = tgts.is_a?(Array) ? tgts : find(tgts)
  139. 7 targets.size > 0
  140. end
  141. 2 alias_method :exist?, :exists?
  142. # Similar to the exists? method, this will return true only if the given name
  143. # resolves to a single valid target.
  144. 2 def unique?(name)
  145. 2 tgts = resolve_mapping(name)
  146. 2 targets = tgts.is_a?(Array) ? tgts : find(tgts)
  147. 2 targets.size == 1
  148. end
  149. # Switch to the supplied target, name can be a fragment as long as it allows
  150. # a unique target to be identified.
  151. #
  152. # The name can also be a MOO number mapping from the config.production_targets
  153. # attribute of the application.
  154. #
  155. # Calling this method does not affect the default target setting in the workspace.
  156. #
  157. # The target can also be set to a proc to be called instead, this is really
  158. # intended to be used for testing purposes:
  159. # Origen.target.temporary = -> { $dut = SomeLocalClass.new }
  160. 2 def temporary=(name)
  161. 259 if name.is_a?(Proc)
  162. 64 self.file = nil
  163. 64 @proc = name
  164. 64 return
  165. else
  166. 195 @proc = nil
  167. end
  168. 195 tgts = resolve_mapping(name)
  169. 195 targets = tgts.is_a?(Array) ? tgts : find(tgts)
  170. 195 if targets.size == 0
  171. 2 puts "Sorry no targets were found matching '#{name}'!"
  172. 2 puts 'Here are the available options:'
  173. 2 find('').sort.each do |file|
  174. 34 puts File.basename(file)
  175. end
  176. 2 exit 1
  177. 193 elsif targets.size > 1
  178. if is_a_moo_number?(name) && prod_targets
  179. puts "Multiple production targets exist for #{name.upcase}, use one of the following instead of the MOO number:"
  180. targets.sort.each do |file|
  181. puts File.basename(file)
  182. end
  183. else
  184. puts 'Please try again with one of the following targets:'
  185. targets.sort.each do |file|
  186. puts File.basename(file)
  187. end
  188. end
  189. exit 1
  190. else
  191. 193 self.file = targets[0]
  192. end
  193. end
  194. 2 alias_method :switch, :temporary=
  195. 2 alias_method :switch_to, :temporary=
  196. 2 def proc
  197. 458 @proc
  198. end
  199. # Returns a signature for the current target, can be used to track target
  200. # changes in cases where the name is not unique - i.e. when using a
  201. # configurable target
  202. 2 def signature
  203. 31 @signature ||= set_signature(nil)
  204. end
  205. # @api private
  206. 2 def set_signature(options)
  207. 394 options ||= {}
  208. 394 @signature = options.merge(_tname: name).to_a.hash
  209. end
  210. # As #temporary= except that the given target will be set to the workspace default
  211. 2 def default=(name)
  212. 1 if name
  213. 1 self.temporary = name
  214. else
  215. @file = nil
  216. end
  217. 1 save
  218. end
  219. # Prints out the current target details to the command line
  220. 2 def describe
  221. f = self.file!
  222. puts "Current target: #{f.basename}"
  223. puts '*' * 70
  224. File.open(f).each do |line|
  225. puts " #{line}"
  226. end
  227. puts '*' * 70
  228. end
  229. # Returns an array of matching target file paths
  230. 2 def find(name)
  231. 212 if name
  232. 211 name = name.gsub('*', '')
  233. 211 if File.exist?(name)
  234. [name]
  235. 211 elsif File.exist?("#{Origen.root}/target/#{name}") && name != ''
  236. 2 ["#{Origen.root}/target/#{name}"]
  237. else
  238. # The below weirdness is to make it recurse into symlinked directories
  239. 209 Dir.glob("#{DIR}/**{,/*/**}/*").sort.uniq.select do |file|
  240. 3554 File.basename(file) =~ /#{name}/ && file !~ /.*\.rb.+$/
  241. end
  242. end
  243. else
  244. 1 [nil]
  245. end
  246. end
  247. # Saves the current target as the workspace default, i.e. the current target
  248. # will be used by Origen the next time if no other target is specified
  249. 2 def save # :nodoc:
  250. 1 if @file
  251. 1 File.open(SAVE_FILE, 'w') do |f|
  252. 1 Marshal.dump(file, f)
  253. end
  254. else
  255. forget
  256. end
  257. end
  258. # Load the default file from the workspace default if it exists and return it,
  259. # otherwise returns nil
  260. 2 def default_file
  261. 183 return @default_file if @default_file
  262. 1 if File.exist?(SAVE_FILE)
  263. 1 File.open(SAVE_FILE) do |f|
  264. 1 @default_file = Marshal.load(f)
  265. end
  266. elsif File.exist?(DEFAULT_FILE)
  267. @default_file = Pathname.new(DEFAULT_FILE)
  268. end
  269. 1 @default_file
  270. end
  271. # Returns the target file (a Pathname object) if it has been defined, otherwise nil
  272. 2 def file # :nodoc:
  273. 1646 return @file if @file
  274. 61 if default_file && File.exist?(default_file)
  275. 61 @file = default_file
  276. end
  277. end
  278. # As file except will raise an exception if it hasn't been defined yet
  279. 2 def file! # :nodoc:
  280. 330 unless file
  281. puts 'No target has been specified!'
  282. puts 'To specify a target use the -t switch.'
  283. puts 'Look in the target directory for a list of available target names.'
  284. exit 1
  285. end
  286. 330 file
  287. end
  288. 2 def file=(path) # :nodoc:
  289. 257 if path
  290. 192 @file = Pathname.new(path)
  291. else
  292. 65 @file = nil
  293. end
  294. end
  295. # Remove the workspace default target
  296. 2 def forget
  297. File.delete(SAVE_FILE) if File.exist?(SAVE_FILE)
  298. @default_file = nil
  299. end
  300. # # This attribute is used by the Origen#compile and Origen#merge tasks to allow files
  301. # # to be compiled on a per-target basis. If the ERB source file has 'target' in the
  302. # # name then this will be substituted for the value returned from this attribute. <br>
  303. # # For example to simply use the MOO number to identify the target you may
  304. # # set up a simple task like this:
  305. # # # Compile the J750 files for each target
  306. # # Target.each_production do
  307. # # $target.id = $target.moo.gsub("*","")
  308. # # compile("templates/j750", "j750")
  309. # # end
  310. # attr_accessor :id
  311. #
  312. # def initialize # :nodoc:
  313. # restore
  314. # end
  315. #
  316. # # Yields a summary of the current target settings
  317. # def summary
  318. # yield "Top: #{$top.class}"
  319. # yield "SoC: #{$soc.class}"
  320. # yield "Tester: #{$tester.class}"
  321. # end
  322. # Returns true if running with a temporary target, i.e. if the current
  323. # target is not the same as the default target
  324. 2 def temporary?
  325. @file == @default_file
  326. end
  327. # Resolves the target name to a target file if a MOO number is supplied and
  328. # app.config.production_targets has been defined
  329. 2 def resolve_mapping(name) # :nodoc:
  330. 204 if is_a_moo_number?(name) && prod_targets
  331. # If an exact match
  332. 10 if prod_targets[name.upcase]
  333. 5 prod_targets[name.upcase]
  334. # If a wildcard match
  335. 5 elsif prod_targets["*#{moo_number_minus_revision(name)}"]
  336. prod_targets["*#{moo_number_minus_revision(name)}"]
  337. # Else just return the given name
  338. else
  339. 5 name
  340. end
  341. else
  342. 194 name
  343. end
  344. end
  345. # Returns config.production_targets with all keys forced to upper case
  346. 2 def prod_targets # :nodoc:
  347. 33 return {} unless Origen.config.production_targets
  348. 33 return @prod_targets if @prod_targets
  349. 2 @prod_targets = {}
  350. 2 Origen.config.production_targets.each do |key, value|
  351. 6 @prod_targets[key.upcase] = value
  352. end
  353. 2 @prod_targets
  354. end
  355. # Returns true if the supplied target name is a moo number format
  356. 2 def is_a_moo_number?(name) # :nodoc:
  357. 204 !!(name.to_s.upcase =~ /^\d?\d?\*?[A-Z]\d\d[A-Z]$/)
  358. end
  359. 2 def moo_number_minus_revision(name) # :nodoc:
  360. 5 name.to_s.upcase =~ /^\d?\d?([A-Z]\d\d[A-Z])$/
  361. 5 Regexp.last_match[1]
  362. end
  363. end
  364. end
  365. end

lib/origen/application/workspace_manager.rb

36.36% lines covered

77 relevant lines. 28 lines covered and 49 lines missed.
    
  1. 2 module Origen
  2. 2 class Application
  3. 2 class WorkspaceManager
  4. # Returns the directory that contains the current application's revision control
  5. # root (basically just Origen.app.rc.root.parent)
  6. 2 def container_directory
  7. 4 if Origen.running_on_windows?
  8. dir = revision_control_root.parent
  9. Pathname.new(dir.to_s.sub(/\/$/, ''))
  10. else
  11. 4 revision_control_root.parent
  12. end
  13. end
  14. # Returns the path to the root directory of the revision control system
  15. # that is managing the application.
  16. #
  17. # This may not necessarily be Origen.root if the application is embedded within
  18. # a larger project workspace (for example in tool_data/origen)
  19. 2 def revision_control_root
  20. 8 Origen.app.rc ? Origen.app.rc.root : Origen.root
  21. end
  22. # Origen.root may not necessarily be the same as the revision control root.
  23. # This method will return the relative path from the revision control root to
  24. # Origen.root.
  25. 2 def path_to_origen_root
  26. path = Origen.root.to_s.sub(revision_control_root.to_s, '').sub(/^(\/|\\)/, '')
  27. path = '.' if path.empty?
  28. path
  29. end
  30. # Provides a proposal for where the reference workspace should live
  31. 2 def reference_workspace_proposal
  32. "#{container_directory}/#{Origen.app.name}_reference"
  33. end
  34. # Returns the path to the actual reference workspace if it is set,
  35. # otherwise returns nil
  36. 2 def reference_workspace
  37. if reference_workspace_set?
  38. dir = File.readlink(reference_dir)
  39. dir.gsub!('.ref', '')
  40. dir.gsub!(/#{Regexp.escape(path_to_origen_root)}\/?$/, '')
  41. Pathname.new(dir).cleanpath
  42. end
  43. end
  44. # Returns the path to the directory that will be used to contain
  45. # all imported application workspaces
  46. 2 def imports_directory
  47. return @imports_directory if @imports_directory
  48. old = "#{container_directory}/#{revision_control_root.basename}_imports_DO_NOT_HAND_MODIFY"
  49. @imports_directory = "#{container_directory}/.#{revision_control_root.basename}_imports_DO_NOT_HAND_MODIFY"
  50. FileUtils.rm_rf(old) if File.exist?(old)
  51. @imports_directory
  52. end
  53. # Returns the path to the directory that will be used to contain
  54. # all remotes workspaces
  55. 2 def remotes_directory
  56. 4 return @remotes_directory if @remotes_directory
  57. 2 old = "#{container_directory}/#{revision_control_root.basename}_remotes_DO_NOT_HAND_MODIFY"
  58. 2 @remotes_directory = "#{container_directory}/.#{revision_control_root.basename}_remotes_DO_NOT_HAND_MODIFY"
  59. 2 FileUtils.rm_rf(old) if File.exist?(old)
  60. 2 @remotes_directory
  61. end
  62. # Returns true if the local reference directory is already
  63. # pointing to an external workspace.
  64. 2 def reference_workspace_set?
  65. f = reference_dir
  66. File.exist?(f) && File.symlink?(f) &&
  67. File.exist?(File.readlink(f))
  68. end
  69. 2 def set_reference_workspace(workspace)
  70. f = reference_dir
  71. if File.exist?(f)
  72. if File.symlink?(f)
  73. FileUtils.rm_f(f)
  74. else
  75. FileUtils.rm_rf(f)
  76. end
  77. end
  78. remote_ref = "#{origen_root(workspace)}/.ref"
  79. unless File.exist?(remote_ref)
  80. FileUtils.mkdir_p(remote_ref)
  81. `touch #{remote_ref}/dont_delete` # Make sure the pop does not blow this away
  82. end
  83. if Origen.running_on_windows?
  84. system("call mklink /h #{reference_dir} #{remote_ref}")
  85. else
  86. File.symlink(remote_ref, reference_dir)
  87. end
  88. end
  89. # Returns the full path to Origen.root within the given workspace
  90. 2 def origen_root(workspace)
  91. Pathname.new("#{workspace}/#{path_to_origen_root}").cleanpath
  92. end
  93. 2 def reference_dir
  94. "#{Origen.root}/.ref" # Should probably be set by a config parameter
  95. end
  96. # Builds a new workspace at the given path
  97. 2 def build(path, options = {})
  98. options = {
  99. rc_url: Origen.app.config.rc_url || Origen.app.config.vault,
  100. allow_rebuild: false
  101. }.merge(options)
  102. if File.exist?(path.to_s) && !options[:allow_rebuild]
  103. fail "Sorry but #{path} already exists!"
  104. end
  105. FileUtils.rm_rf(path.to_s) if File.exist?(path.to_s)
  106. rc = RevisionControl.new options.merge(remote: options[:rc_url], local: path.to_s)
  107. rc.build
  108. end
  109. # Switches the given workspace path to the given version tag
  110. 2 def switch_version(workspace, tag, options = {})
  111. options = {
  112. origen_root_only: false, # When true pop the Origen.root dir only instead
  113. # of the whole application workspace - these may or may
  114. # not be the same thing depending on the application.
  115. }.merge(options)
  116. version_file = "#{workspace}/.current_version"
  117. FileUtils.rm_f(version_file) if File.exist?(version_file)
  118. if options[:origen_root_only]
  119. dir = "#{workspace}/#{path_to_origen_root}"
  120. else
  121. dir = workspace
  122. end
  123. rc_url = Origen.app.config.rc_url || Origen.app.config.vault
  124. rc = RevisionControl.new remote: rc_url, local: dir.to_s
  125. rc.checkout version: tag, force: true
  126. File.open(version_file, 'w') do |f|
  127. f.puts tag
  128. end
  129. end
  130. 2 def current_version_of(workspace)
  131. 2 f = "#{workspace}/.current_version"
  132. 2 if File.exist?(f)
  133. 2 File.readlines(f).first.strip
  134. end
  135. end
  136. end
  137. end
  138. end

lib/origen/bugs/bug.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Bugs
  3. 2 class Bug
  4. 2 attr_reader :affected_versions
  5. 2 attr_reader :name
  6. 2 alias_method :id, :name
  7. 2 def initialize(name, options = {})
  8. 4 @name = name
  9. 4 @affected_versions = [options[:affected_version] || options[:affected_versions]].flatten.compact
  10. 4 @fixed_on_version = options[:fixed_on_version]
  11. end
  12. 2 def present_on_version?(version, _options = {})
  13. 12 if affected_versions.empty?
  14. 6 if fixed_on_version
  15. 3 version < fixed_on_version
  16. else
  17. 3 true
  18. end
  19. else
  20. 6 affected_versions.include?(version)
  21. end
  22. end
  23. 2 def fixed_on_version
  24. 12 @fixed_on_version || begin
  25. 5 unless affected_versions.empty?
  26. 1 affected_versions.max + 1
  27. end
  28. end
  29. end
  30. end
  31. end
  32. end

lib/origen/chip_mode.rb

83.56% lines covered

73 relevant lines. 61 lines covered and 12 lines missed.
    
  1. 1 module Origen
  2. # Represents an SoC DFT/Operating mode - e.g. SCAN, RAMBIST, etc.
  3. 1 class ChipMode
  4. 1 attr_accessor :brief_description
  5. 1 attr_accessor :description
  6. 1 attr_writer :name
  7. 1 attr_accessor :data_rate
  8. 1 attr_accessor :data_rate_unit
  9. 1 attr_accessor :minimum_version_enabled
  10. 1 alias_writer :min_ver_enabled, :minimum_version_enabled
  11. 1 alias_writer :min_version_enabled, :minimum_version_enabled
  12. 1 attr_accessor :audience
  13. 1 alias_writer :full_name, :name
  14. # Returns the object that owns the mode (the SoC instance usually)
  15. 1 attr_accessor :owner
  16. 1 attr_accessor :typical_voltage
  17. 1 alias_method :typ_voltage, :typical_voltage
  18. 1 def initialize(name, options = {})
  19. 426 options.each { |k, v| instance_variable_set("@#{k}", v) }
  20. 382 (block.arity < 1 ? (instance_eval(&block)) : block.call(self)) if block_given?
  21. 382 @name = name
  22. 382 validate_args
  23. end
  24. 1 def name
  25. 842 @name || @id
  26. end
  27. 1 alias_method :full_name, :name
  28. 1 def id
  29. 805 @id || name.to_s.downcase.gsub(/(\s|-)+/, '_').to_sym
  30. end
  31. 1 def id=(val)
  32. @id = val.to_s.gsub(/(\s|-)+/, '_').downcase.to_sym
  33. end
  34. 1 def data_rate(options = {})
  35. options = {
  36. 3 absolute_number: true
  37. }.update(options)
  38. # Convert the data rate to a number
  39. 3 if !!@data_rate && !!@data_rate_unit
  40. 2 if options[:absolute_number]
  41. # The data rate unit was validated on init so it is good to go
  42. # in theory but should still check if it returns a numeric
  43. 1 value = @data_rate.send(@data_rate_unit.to_sym)
  44. 1 if value.is_a?(Numeric)
  45. 1 return value
  46. else
  47. Origen.log.error "@data_rate '#{@data_rate}' conversion using @data_rate_unit '#{@data_rate_unit}' did not product a Numeric, exiting..."
  48. end
  49. else
  50. 1 return @data_rate
  51. end
  52. else
  53. 1 return @data_rate
  54. end
  55. end
  56. 1 def respond_to_missing?(method_name, _include_private = false)
  57. method_name[-1] == '?'
  58. end
  59. # Implements methods like:
  60. #
  61. # if $dut.mode.rambist?
  62. 1 def method_missing(method_name, *arguments, &block)
  63. 20 ivar = "@#{method_name.to_s.gsub('=', '')}"
  64. 20 ivar_sym = ":#{ivar}"
  65. 20 if method_name[-1] == '?'
  66. return id == method_name[0..-2].to_sym
  67. 20 elsif method_name[-1] == '='
  68. 16 define_singleton_method(method_name) do |val|
  69. 16 instance_variable_set(ivar, val)
  70. end
  71. 4 elsif instance_variables.include? ivar_sym
  72. instance_variable_get(ivar)
  73. else
  74. 4 define_singleton_method(method_name) do
  75. 4 instance_variable_get(ivar)
  76. end
  77. end
  78. 20 send(method_name, *arguments, &block)
  79. end
  80. 1 def to_s
  81. id.to_s
  82. end
  83. 1 def to_sym
  84. to_s.to_sym
  85. end
  86. 1 private
  87. 1 def validate_args
  88. 382 unless @data_rate_unit.nil? || @data_rate_unit =~ /n\/a/i || @data_rate_unit =~ /na/i
  89. # Remove special chars
  90. 4 ['/', '-'].each do |special_char|
  91. 8 @data_rate_unit.gsub!(special_char, '')
  92. end
  93. # Check if @data_rate_unit is found in the Numeric core_ext lib
  94. 4 fail "@data_rate_unit '#{@data_rate_unit}' is not an accepted unit" unless 1.send(@data_rate_unit.to_sym)
  95. # Cannot use @data_rate_unit without @data_rate
  96. 4 fail '@data_rate_unit must be set with @data_rate, exiting...' if @data_rate.nil?
  97. end
  98. 382 unless @data_rate.nil? || @data_rate =~ /n\/a/i || @data_rate =~ /na/i
  99. # Check if the data rate was passed as a String, if so convert it to a number
  100. 8 if @data_rate.is_a? String
  101. 4 if @data_rate.numeric?
  102. 4 @data_rate = @data_rate.to_numeric
  103. else
  104. fail "@data_rate '#{@data_rate}' cannot be converted to a number, exiting..."
  105. end
  106. end
  107. end
  108. 382 unless @typical_voltage.nil?
  109. if @typical_voltage.is_a? String
  110. if @typical_voltage.numeric?
  111. @typical_voltage = @typical_voltage.to_numeric
  112. else
  113. fail "@typical_voltage '#{@typical_voltage}' cannot be converted to a number, exiting..."
  114. end
  115. end
  116. end
  117. end
  118. end
  119. end

lib/origen/chip_package.rb

23.73% lines covered

236 relevant lines. 56 lines covered and 180 lines missed.
    
  1. 1 module Origen
  2. # Represents an SoC Package option
  3. 1 class ChipPackage
  4. 1 require 'colored'
  5. 1 attr_accessor :description
  6. 1 attr_accessor :number_of_rows
  7. 1 attr_accessor :number_of_columns
  8. 1 attr_accessor :interconnects
  9. 1 attr_accessor :types
  10. 1 attr_reader :upper_axes
  11. 1 attr_reader :lower_axes
  12. 1 attr_reader :rows
  13. 1 attr_reader :columns
  14. 1 attr_reader :field
  15. 1 attr_reader :obj
  16. 1 attr_reader :groups
  17. 1 attr_reader :group_list
  18. 1 attr_reader :last_empty_char
  19. 1 attr_reader :plottable
  20. 1 attr_writer :name
  21. 1 alias_writer :full_name, :name
  22. # Returns the owner that $owns the mode (the SoC instance usually)
  23. 1 attr_accessor :owner
  24. 1 def name
  25. @name || @id
  26. end
  27. 1 alias_method :full_name, :name
  28. 1 def id
  29. 588 @id || name.to_s.downcase.gsub(/(\s|-)+/, '_').to_sym
  30. end
  31. 1 def id=(val)
  32. 199 @id = val.to_s.gsub(/(\s|-)+/, '_').downcase.to_sym
  33. end
  34. 1 def to_s
  35. id.to_s
  36. end
  37. 1 def to_sym
  38. to_s.to_sym
  39. end
  40. 1 def types
  41. @types || []
  42. end
  43. # prepare_plot should not need to be called explicitly. It is called
  44. # by other methods when need-be, and is fundamentallyis responsible
  45. # for two things:
  46. #
  47. # 1. It checks that self.types includes a "BGA" option.
  48. # 2. It populates the .field attribute with a multi-dimensional
  49. # array, proportional in size to the package BGA.
  50. #
  51. 1 def prepare_plot
  52. @plottable = types.any? { |x| /BGA/i =~ x }
  53. if @plottable
  54. @last_empty_char = '.'
  55. @field = []
  56. @groups = []
  57. @group_list = list_groups
  58. @columns = []
  59. jedec_rows = %w(A B C D E F G H J K L M N P R T U V W Y AA AB AC AD AE AF AG AH AJ AK AL AM AN AP AR AT AU AV AW AY BA BB BC BD BE BF BG BH BJ BK BL BM BN BP)
  60. @number_of_rows.times { @field.insert(0, []); @number_of_columns.times { @field[0].insert(0, []) } }
  61. @rows = jedec_rows[0, @number_of_rows]
  62. (1..@number_of_columns).map { |item| @columns << item }
  63. @upper_axes = []
  64. @lower_axes = []
  65. @columns.each_with_index do|column, index|
  66. # if index % 2 == 0
  67. if index.even?
  68. temp = column.to_s
  69. temp += ' ' unless temp.size > 1
  70. @upper_axes << temp
  71. @lower_axes << ' '
  72. else
  73. temp = column.to_s
  74. temp += ' ' unless temp.size > 1
  75. @lower_axes << temp
  76. @upper_axes << ' '
  77. end
  78. end
  79. else
  80. puts 'Sorry, currently the plot feature only supports BGA package types.'
  81. end
  82. end
  83. # generate_field should not need to be called explicitly. It is called
  84. # by other methods when need-be, and is fundamentallyis responsible
  85. # for two things:
  86. #
  87. # 1. It fills the .field array with the appropriate symbols/markers.
  88. # 2. It concatenates the array elements into printable rows, and prints
  89. # them.
  90. #
  91. 1 def generate_field(emptyChar = @last_empty_char)
  92. if plottable
  93. new_field = []
  94. @field.each do |rows|
  95. rows.each do |items|
  96. if items.length == 0
  97. items.insert(0, "#{emptyChar} ")
  98. elsif emptyChar != @last_empty_char && items == ["#{last_empty_char} "]
  99. items[0] = "#{emptyChar} "
  100. end
  101. end
  102. new_field.insert(-1, rows.join(''))
  103. end
  104. @last_empty_char = emptyChar
  105. package = (owner.package.nil?) ? 'No package chosen.' : owner.package.to_s
  106. puts "\nPin field: #{package}\n\n"
  107. group_display = @groups.join("\n")
  108. puts "Legend: \n#{group_display}\n\n"
  109. puts @upper_axes.join('').yellow
  110. new_field.each_with_index { |line, index| puts line + "#{@rows[index]} (#{index + 1})\n".chop.yellow }
  111. puts @lower_axes.join('').yellow, "\n"
  112. end
  113. end
  114. 1 alias_method :show, :generate_field
  115. 1 def add_power(marker = 'P')
  116. # add_power can be called explicitly or by the .plot("power") method call.
  117. prepare_plot if field.nil?
  118. if plottable
  119. pin_list = owner.power_pins.map { |_ken, pin| pin }
  120. @groups << "#{marker} - Power"
  121. pin_list.each do |item|
  122. # puts items,owner.pins[items].location
  123. begin
  124. coordinates = coordinate(item.location)
  125. @field[coordinates[0]][coordinates[1]] = [marker.red + ' ']
  126. rescue
  127. puts "#{item} doesn't appear to have a physical location in this configuration."
  128. puts "Current package = #{owner.package}"
  129. end
  130. end
  131. generate_field
  132. end
  133. end
  134. 1 alias_method :add_powers, :add_power
  135. 1 alias_method :plot_power, :add_power
  136. 1 alias_method :plot_powers, :add_power
  137. 1 def add_grounds(marker = 'G')
  138. # add_grounds can be called explicitly, or by the .plot("grounds") method call.
  139. prepare_plot if field.nil?
  140. if plottable
  141. pin_list = owner.ground_pins.map { |_ken, pin| pin }
  142. @groups << "#{marker} - Ground"
  143. pin_list.each do |item|
  144. begin
  145. coordinates = coordinate(item.location)
  146. @field[coordinates[0]][coordinates[1]] = [marker.green + ' ']
  147. rescue
  148. puts "#{item} doesn't appear to have a physical location in this configuration."
  149. end
  150. end
  151. generate_field
  152. end
  153. end
  154. 1 alias_method :add_ground, :add_grounds
  155. 1 alias_method :plot_ground, :add_grounds
  156. 1 alias_method :plot_grounds, :add_grounds
  157. 1 def clear
  158. # clear removes all elements from the .field and .groups attributes.
  159. prepare_plot if field.nil?
  160. if plottable
  161. @groups = []
  162. @field = []
  163. @number_of_rows.times { @field.insert(0, []); @number_of_columns.times { @field[0].insert(0, []) } }
  164. end
  165. end
  166. 1 def plot_help
  167. prepare_plot if field.nil?
  168. puts "\n#################### PLOT HELP ####################"
  169. puts 'To generate in-console BGA plots, the ChipPackage class will respond to the following methods:'
  170. puts '.list_groups, .plot(), .plot_coord(), .show(), and .clear'
  171. if plottable
  172. puts "\nPLOTTING GROUPS:"
  173. puts '$dut.package.list_groups <-- to see available group names'
  174. puts "$dut.package.plot(\"ddr_interface_1\")"
  175. puts "$dut.package.plot_group(\"serdes_1\",'Z') <--denotes custom legend marker, Z"
  176. puts "\nPLOTTING INDIVIDUAL PINS:"
  177. puts "$dut.package.plot(\"d1_mdqs00\")"
  178. puts "\nPLOTTING WITH REGEXP:"
  179. puts "$dut.package.plot(\"d1_mdqs\") <-- Plot all controller 1 DQS pins."
  180. puts "$dut.package.plot(\"d1_mdqs0[0-9]\") <-- Plot d1_mdqs00 - d1_mdqs09."
  181. puts "\nADDING POWER/GROUND:"
  182. puts "$dut.package.plot(\"grounds\")"
  183. puts "$dut.package.plot(\"power\")"
  184. puts "\nVIEW CURRENT PLOT\n"
  185. puts '$dut.package.show'
  186. else
  187. puts 'Currently, only BGA package types are supported for in-console plotting.'
  188. end
  189. end
  190. 1 alias_method :help_plot, :plot_help
  191. 1 def list_groups
  192. # returns an array of group names assigned to the package
  193. grps = owner.pins.map { |_key, val| val.group }
  194. grps.uniq!
  195. rescue
  196. return []
  197. end
  198. 1 alias_method :group_list, :list_groups
  199. 1 def group_array(grp)
  200. # returns an array of pins belonging to the given group
  201. pin_list = $dut.pins.map { |_key, val| val if val.group == grp }
  202. pin_list[0].compact!
  203. puts 'No pins found under that group name.' unless pin_list.any?
  204. rescue
  205. return []
  206. end
  207. # ##############################################################
  208. # ############# String/Coordinate Manipulation #################
  209. # ##############################################################
  210. 1 def coordinate(location)
  211. ## Returns array of numerical equiv coordinates (e.g. "AA11" -> [20,11])
  212. error = "\n\nSomething wrong during coordinate-mapping.\nAre you sure you passed an alphanumeric string\n to the coordinate() method?\n"
  213. split_index = -1
  214. location.each_char do |character|
  215. if letter?(character)
  216. split_index += 1
  217. end
  218. end
  219. row = location[0..split_index]
  220. column = location[split_index + 1..-1]
  221. fail ArgumentError, error unless row.length > 0 && column.length > 0
  222. ## Now convert alphanumeric row to jedec equiv' with to_row() method
  223. # and return coordinates.
  224. coordinates = [to_row(row), column.to_i - 1]
  225. end
  226. 1 def to_row(alphanumeric_coord)
  227. ## This maps alpha row coordinate to its appropriate Jedec:
  228. number = alphanumeric_coord.upcase.tr('A-HJ-NPRT-WY', '1-9a-q').to_i(21) - 1
  229. number -= (number / 21)
  230. end
  231. 1 def letter?(test_character)
  232. ## Returns nil if the 'test_character' isn't a letter.
  233. test_character =~ /[[:alpha:]]/
  234. end
  235. 1 def initial(test_string)
  236. test_string[0, 1]
  237. end
  238. # .plot can be called explicitly and accepts string arguments with
  239. # or without regex styling. For example:
  240. #
  241. # $dut.package = :t4240
  242. # $dut.package.plot("ddr_interface") # plots all "ddr interface" groups
  243. # $dut.package.plot("grounds") # adds ground pins to the previously instantiated plot
  244. # $dut.package.plot("d1_mdq37","$") # plots controller 1 mdq 37, and uses $ as a legend marker
  245. # $dut.package.plot("d2_mdq[3-6]0) # plots d2_mdq30, d2_mdq40, d2_md520, and d2_md620
  246. #
  247. 1 def plot(pinName, marker = nil)
  248. prepare_plot if field.nil?
  249. if plottable && pinName.is_a?(String) && /ground/ =~ pinName.downcase
  250. add_grounds('G')
  251. elsif plottable && pinName.is_a?(String) && /power/ =~ pinName.downcase
  252. add_power('P')
  253. elsif plottable && pinName.is_a?(String)
  254. found_pins = []
  255. owner.pins.map { |pin| found_pins << pin[1] if /#{pinName}/ =~ pin[1].name.to_s || /#{pinName}/ =~ pin[1].group.to_s }
  256. if found_pins.size == 1 && marker.nil?
  257. marker = initial(pinName.to_s)
  258. while @groups.index { |grpName| grpName =~ /#{marker} -/ }
  259. marker.next!
  260. marker = '0' unless marker.size < 2
  261. end
  262. coordinates = coordinate(found_pins[0].location)
  263. @field[coordinates[0]][coordinates[1]] = [marker.white_on_blue + ' ']
  264. @groups.delete_if { |group| /#{pinName.to_s}/ =~ group }
  265. @groups << "#{marker} - #{found_pins[0].name} - #{found_pins[0].location}"
  266. elsif found_pins.size == 1
  267. coordinates = coordinate(found_pins[0].location)
  268. @field[coordinates[0]][coordinates[1]] = [marker.white_on_blue + ' ']
  269. @groups.delete_if { |group| /#{pinName.to_s}/ =~ group }
  270. @groups << "#{marker} - #{found_pins[0].name} - #{found_pins[0].location}"
  271. else
  272. if marker.nil?
  273. marker = initial(pinName.to_s)
  274. while @groups.index { |grpName| grpName =~ /#{marker} -/ }
  275. marker.next!
  276. marker = '0' unless marker.size < 2
  277. end
  278. end
  279. reg_state = quote_regex(pinName)
  280. found_pins.each do |item|
  281. begin
  282. coordinates = coordinate(item.location)
  283. @field[coordinates[0]][coordinates[1]] = [marker + ' ']
  284. @groups.delete_if { |group| "#{marker} - \"#{reg_state}\"" == group }
  285. @groups << "#{marker} - \"#{pinName}\""
  286. rescue
  287. raise "\n#{item} doesn't appear to have a physical location in this configuration."
  288. end
  289. end
  290. end
  291. generate_field
  292. else
  293. puts 'Unsupported argument type.'
  294. end
  295. end
  296. # .plot_coord can be called explicitly and accepts string arguments in the form
  297. # of jedec standard BGA coordinate naming conventions.
  298. #
  299. # $dut.package = :t4240
  300. # $dut.package.plot_coord("A2")
  301. #
  302. 1 def plot_coord(coord, marker = nil)
  303. prepare_plot if field.nil?
  304. if plottable
  305. if coord.is_a?(String)
  306. found_pins = []
  307. owner.pins.map { |pin| found_pins << pin[1] if coord == pin[1].location.to_s }
  308. if marker.nil?
  309. marker = initial(coord.to_s)
  310. while @groups.index { |grpName| grpName =~ /#{marker} -/ }
  311. marker.next!
  312. marker = '0' unless marker.size < 2
  313. end
  314. found_pins.each do |pin|
  315. coordinates = coordinate(pin.location)
  316. @field[coordinates[0]][coordinates[1]] = [marker.white_on_blue + ' ']
  317. @groups.delete_if { |group| /#{coord.to_s}/ =~ group }
  318. @groups << "#{marker} - #{found_pins[0].name} - #{found_pins[0].location}"
  319. end
  320. else
  321. coordinates = coordinate(found_pins[0].location)
  322. @field[coordinates[0]][coordinates[1]] = [marker.white_on_blue + ' ']
  323. @groups.delete_if { |group| /#{coord.to_s}/ =~ group }
  324. @groups << "#{marker} - #{found_pins[0].name} - #{found_pins[0].location}"
  325. end
  326. if found_pins.size > 0
  327. generate_field
  328. else
  329. puts "Coordinate not recognized. Jedec convention: <row><col>. E.g., A1.\nCould be power/ground pin. .plot(\"power\") or .plot(\"ground\")."
  330. end
  331. else
  332. puts 'Unsupported argument type.'
  333. end
  334. end
  335. end
  336. 1 alias_method :plot_coordinate, :plot_coord
  337. 1 def quote_regex(regex_statement)
  338. with_escapes = regex_statement
  339. with_escapes.gsub!('[', '\[')
  340. with_escapes.gsub!(']', '\]')
  341. with_escapes.gsub!('^', '\^')
  342. with_escapes
  343. end
  344. 1 def plot_ceo
  345. scramble = [' +....~~~~~:,,..::~~:,......,:~==~,,....,:~==~~::,:~~,., ',
  346. ' ...++~:,,:~~~~~======~::::,,,,::=======~~::,, ',
  347. '.................????????+,,,,::::::,,,.,,..,,,,:::????......................:+',
  348. ' ,.,:~~~~~:,~~~~:~:~~=~:~~~==++====::~~~===~~=~~==:,, ',
  349. ' ,....~++?:::,,::::~~~=======+============~~~:,~ ',
  350. ' ....+++,:,,::::~~~==========~===========~:,:,~ ',
  351. ' ,........~~~~~~============================~........: ',
  352. ' ......,~~~~~~~===~++==+=+++++++++++++++++====~===~~~.....~ ',
  353. ' ,,,::::::~~~~:,,:::,,,,:,,,,......:~~:~::,,: ',
  354. ' .....,::~~~~=============+++++++++++++++++=======~~~:..... ',
  355. ' +.,::::::~~~=~=~~~~~~~====~~~~~:::~~==~~::::, ',
  356. ' .......:~~~~=~=======++++++++++++++++++======~~........ ',
  357. ' +:,,::~===+++++====+====~~~=====~====+++++++++=~~,~+ ',
  358. '.....................,===+??????? ,..........????????=:,....................',
  359. '...................=????????++++:,::::::::::::::???????~.......................',
  360. '......................+++++?????? .............=??????+:,....................',
  361. ' =~:,::~~++=~=::,,,,,,,,,: ',
  362. ' +.........,,,,,,...,,,:,,,........,,,........,? ',
  363. ' :....::~~~=========+++++++++++++++++++++++=======~~:.... ',
  364. '...................,+???????????? ?::,:,,,,,,::????????=,......................',
  365. '..................~???????++++,::,::::::~~:::::::+?????:.......................',
  366. ' :......:~~~~~~~=====+++++++++++++++++++++=+=====~~~......= ',
  367. ' ::,::::~~===++===:.,~~~~~~===~~~~~,,:======~~::, ',
  368. ' ,,,::::~~~~~~~~~=~~===~~~~===~=====~~~~~~:::,~ ',
  369. '....................::~+??????? =~+ ?????+?+=,.....................',
  370. ' :,.,~~~~~,:~~~......~,,..:~=+++=~:,..:,.......:~~=~:,~ ',
  371. ' :........:~~~~~~~~~===============~~~====:,....... ',
  372. ' ::,::~~===+++++++++==~~~~=======~~====+++++==~~::: ',
  373. ' ....,:~~~~~========++++++++++++++++++++++=+=====~~~:.... ',
  374. ' ,......,~~~~~~~=======+++++++++++++++++++=====~~~....... ',
  375. ' ,........+???~:::,,,,,:~~~~==============~~~~:,,?...? ',
  376. ' ...,~~~~~,.,~=====~:,.,.,:~==+==:,,,,.,,.,,~==~::=~,,~ ',
  377. ' ,........,:::::,:::::~~~~~~~:::,,,,,,~::.......,= ',
  378. ' ::,::~:~~======~:.,~::,,,,,,,,,,,~=.,:~===~~~::, ',
  379. '.........................???++?????====.........?????~,??+::,..................',
  380. ' ......:~:~~~=~=========+=++++++++++++++++=======~~~~:..... ',
  381. ' ,...,..,.,,.,,,,,,.:::,.,,........? ',
  382. '.......................?++??? ...............??????+::....................',
  383. '...........................?+????? ??~....,...+?????????=::..................',
  384. ' ......:~:~~~~~====~======~==++===+++++===========~~~.....~ ',
  385. '..........................+????? ?????+=........??????????~::..................',
  386. ' ....::~~~====+++++=======++++++++++==============~:...~ ',
  387. '..........................?+?????? ?++=,........????????~+:::..................',
  388. ' .......:~~~~~~======+++++++++++++++++++++=====~~~:...... ',
  389. '.......................~?????? :.......,.........??????~:,...................',
  390. ' ,,:::::~~=~~~:,~=~~~~~~:~~~~~~~====:~~=~~::::: ',
  391. ' ~,,:::::~=====:..~~~,.,:,,.,,,.~==~,:~===~~::,:= ',
  392. ' :,....::::,:,,,,,,,.,,,,,,..,:= ',
  393. ' ..............+?????=,,,,::,,,::~:~~~~~~~~~:::::,,::??..........~ ',
  394. ' ,,.:~~====~~~==~=~~~=====~~==+======~~~~~~===~===:,: ',
  395. ' ........~~~~~======+++++++++++++++++++=======~~....... ',
  396. ' ....::~~~~===============================~~~:~===~:... ',
  397. ' ,...........,...................,..........: ',
  398. ' +....:~~~~~~~,,..,,,,,.,:~~~~~===~~~~:,.,,,,,,,:~~~~.., ',
  399. ' .......~~~~~~~~=~==+==+++++++++++++=++++++==~====~~,.....: ',
  400. '.................~???????+++,,,:,::::::,,,,,:,:::,?????........................',
  401. ' ~,.,:~==================~~~==+==~=========++++==~::: ',
  402. ' ,....,,,,,......,,,.,..:,:,..........:~ ',
  403. '........................?????? ++=.,,:.........++=:?????=::...................',
  404. ' .......+++?:~:,,,,::~~~==================~~~:,,.? ',
  405. '...................,:+?????????????? =:~:::::~ ???????++:......................',
  406. ' :....,::~~~~==~====+=+++++++++==+++++++++========~~~:....: ',
  407. '.....................~~~+?????? ~:....: ????????=:.....................',
  408. '....................::+?????????????????::,. ????????++~......................',
  409. ' ,...,,:,.,................,,::..........,= ',
  410. ' ~::,:~~~====+++++==~:~~~~==++===~=::~==++++==:::,: ',
  411. ' ~,.,~~~~~~~,..:~...~=~::,~~=++++=~~::~~~~~~:,,,~==~:, ',
  412. '........................~??????=+?+~:~~.........???..????=::...................',
  413. ' ,..........+????:::,::,,,:~~~~~~==~~~~~~~~~~~~,.:=?......: ',
  414. '................=???????~,,,::,:,:,,,.......,,,,,,:~???..................= ',
  415. ' ,,::::::~~~~~~~~~~=====~~~~=====~~~~=~~~::::, ',
  416. ' ........~~~~~========+++++++==++++=========~~........ ',
  417. ' :..+,,,:~~~~~======~~~==~===~~~~======~~~:::, ',
  418. ' ~.,,::~:~~~~~~~~~~~~~~~=======~=====~~~~::,: ',
  419. ' ..............:??????+,,,,:,,,,:,,,,,,,,....,..,,::~??.............., ',
  420. ' .,:~~:~:::::.....,:~=====~:::~~~:,::~~::,, ']
  421. pw = [16, 47, 75, 168, 53, 36, 57, 116, 94, 21, 64, 52, 13, 95, 1, 17, 32, 38, 4, 142,
  422. 26, 6, 89, 134, 44, 71, 50, 40, 170, 149, 11, 29, 167, 27, 120, 43, 21, 107, 72, 14, 54,
  423. 7, 3, 58, 55, 39, 35, 47, 113, 5, 9, 61, 162, 123, 39, 28, 18, 36, 35, 91, 41, 51, 160,
  424. 128, 54, 53, 0, 138, 165, 125, 31, 25, 19, 155, 25, 66, 3, 15, 49, 96, 49, 56, 158, 100,
  425. 147, 12, 27, 101, 56, 12, 65, 22, 124, 106, 11, 33, 46, 26, 103, 7, 45, 23, 46, 97, 9,
  426. 70, 10, 109, 119, 22, 8, 75, 151, 65, 52, 73, 72, 99, 30, 17, 1, 5, 90, 76, 81, 4, 59,
  427. 50, 82, 84, 30, 68, 102, 148, 80, 48, 74, 129, 137, 132, 69, 2, 140, 34, 144, 55, 20,
  428. 150, 24, 143, 14, 19, 86, 8, 67, 60, 63, 2, 62, 146, 24, 62, 0, 104, 68, 13, 15, 173, 79,
  429. 63, 37, 44, 93, 85, 60, 58, 67]
  430. y = []
  431. (0..71).each { |n| y << n + n / 2 * 3 }
  432. y.each do |z|
  433. puts scramble[pw[z]]
  434. end
  435. 'Oh, hello!'
  436. end
  437. end
  438. end

lib/origen/code_generators.rb

51.25% lines covered

80 relevant lines. 41 lines covered and 39 lines missed.
    
  1. 2 require 'thor/group'
  2. 2 require 'active_support'
  3. 2 require 'active_support/core_ext/object/blank'
  4. 2 require 'active_support/core_ext/kernel/singleton_class'
  5. 2 require 'active_support/core_ext/array/extract_options'
  6. 2 require 'active_support/core_ext/hash/deep_merge'
  7. 2 require 'active_support/core_ext/module/attribute_accessors'
  8. 2 require 'active_support/core_ext/string/inflections'
  9. 2 module Origen
  10. 2 module CodeGenerators
  11. 2 autoload :Base, 'origen/code_generators/base'
  12. 2 autoload :Actions, 'origen/code_generators/actions'
  13. # Remove the color from output.
  14. 2 def self.no_color!
  15. Thor::Base.shell = Thor::Shell::Basic
  16. end
  17. 2 def self.origen_generators
  18. 21 @origen_generators ||= {}
  19. end
  20. 2 def self.plugin_generators
  21. 36 @plugin_generators ||= {}
  22. end
  23. 2 def self.load_generators
  24. 15 return if @generators_loaded
  25. # Load Origen's generators
  26. 1 require_relative 'code_generators/block_common'
  27. 1 require_relative 'code_generators/dut'
  28. 1 require_relative 'code_generators/block'
  29. 1 require_relative 'code_generators/feature'
  30. 1 require_relative 'code_generators/model'
  31. 1 require_relative 'code_generators/klass'
  32. 1 require_relative 'code_generators/module'
  33. # Load generators from plugins, TBD what the API will be here
  34. 1 @generators_loaded = true
  35. end
  36. # Loaded separately so as not to pollute the generated list of generators available to users
  37. 2 def self.load_internal_generators
  38. return if @internal_generators_loaded
  39. require_relative 'code_generators/semver'
  40. require_relative 'code_generators/timever'
  41. @internal_generators_loaded = true
  42. end
  43. # Receives a namespace, arguments and the behavior to invoke the generator.
  44. # It's used as the default entry point for generate, destroy and update
  45. # commands.
  46. 2 def self.invoke(name, args = ARGV, config = {})
  47. 15 load_generators
  48. 15 if klass = find_by_name(name)
  49. 15 args << '--help' if args.empty? && klass.arguments.any?(&:required?)
  50. 15 klass.start(args, config)
  51. end
  52. end
  53. # Like invoke, but will also make internal-use only generators available
  54. # commands.
  55. 2 def self.invoke_internal(name, args = ARGV, config = {})
  56. load_internal_generators
  57. invoke(name, args, config)
  58. end
  59. 2 def self.find_by_name(name)
  60. 15 names = name.split(':')
  61. 15 case names.size
  62. when 1
  63. 15 gen = origen_generators[names.first]
  64. 15 return gen if gen
  65. when 2
  66. if names.first == 'origen'
  67. gen = origen_generators[names.first]
  68. else
  69. gen = plugin_generators[names.first][names.last]
  70. end
  71. return gen if gen
  72. end
  73. puts "Couldn't find a code generator named: #{name}"
  74. puts
  75. puts 'This is the list of available generators:'
  76. puts
  77. print_generators
  78. puts
  79. end
  80. # Show help message with available generators.
  81. 2 def self.help(command = 'new')
  82. puts <<-END
  83. Add pre-built features and code snippets to your application.
  84. This command will generate code for your application to implement a given feature. In some
  85. cases this will be a complete feature and in others it will provide a starting point for you
  86. to further customize.
  87. END
  88. puts "Usage: origen #{command} FEATURE [args] [options]"
  89. puts
  90. puts 'General options:'
  91. puts " -h, [--help] # Print feature's options and usage"
  92. puts ' -p, [--pretend] # Run but do not make any changes'
  93. puts ' -f, [--force] # Overwrite files that already exist'
  94. puts ' -s, [--skip] # Skip files that already exist'
  95. puts ' -q, [--quiet] # Suppress status output'
  96. puts
  97. puts "The available features are listed below, run 'origen new <feature> -h' for more info."
  98. puts
  99. print_generators
  100. puts
  101. end
  102. 2 def self.print_generators
  103. load_generators
  104. origen_generators.each do |name, _gen|
  105. puts name
  106. end
  107. plugin_generators.each do |namespace, generators|
  108. next if namespace.to_s == 'origen_app_generators'
  109. puts
  110. generators.each do |_name, gen|
  111. puts "#{namespace}:#{gen}"
  112. end
  113. end
  114. end
  115. end
  116. end

lib/origen/code_generators/actions.rb

51.38% lines covered

218 relevant lines. 112 lines covered and 106 lines missed.
    
  1. 2 require 'open-uri'
  2. 2 require 'set'
  3. 2 module Origen
  4. 2 module CodeGenerators
  5. # Common helpers available to all Origen code generators.
  6. # Some of these have been copied from Rails and don't make a lot of sense in an Origen context,
  7. # however they are being kept around for now as they serve as good examples of how to write
  8. # generator helpers.
  9. 2 module Actions
  10. 2 def initialize(*args) # :nodoc:
  11. 15 if args.last.is_a?(Hash)
  12. 15 @config = args.last.delete(:config) || {}
  13. end
  14. 15 @required_acronyms = Set.new
  15. 15 super
  16. 15 @in_group = nil
  17. end
  18. 2 def config
  19. @config
  20. end
  21. 2 def underscored_app_namespace
  22. 96 Origen.app.namespace.to_s.underscore
  23. end
  24. # Equivalent to calling name.camelcase, but this will identify the need to register any acronyms
  25. # necessary to ensure the camelcased name can be translated back to the original name by the
  26. # underscore method.
  27. # The required acronyms will be saved to an instance variable, @required_acronyms, and calling
  28. # the add_acronyms will add the code to register them to the current application.
  29. 2 def camelcase(name)
  30. 209 name = name.to_s
  31. 209 name.split('_').each do |n|
  32. # Numbers won't be recognized as a split point when going back to underscore, so need to
  33. # register this field beginning with a number as an acronym
  34. 280 @required_acronyms << n if n =~ /^\d/
  35. end
  36. 209 name.camelcase
  37. end
  38. 2 def add_acronyms
  39. 14 unless @required_acronyms.empty?
  40. top_level_file = File.join('app', 'lib', "#{underscored_app_namespace}.rb")
  41. if File.exist?(top_level_file)
  42. require_origen = "require 'origen'\n"
  43. prepend_to_file top_level_file, require_origen
  44. comment = "# The following acronyms are required to ensure that auto-loading works\n# properly with some of this application's class names\n"
  45. insert_into_file top_level_file, comment, after: require_origen
  46. @required_acronyms.each do |acronym|
  47. insert_into_file top_level_file, "Origen.register_acronym '#{acronym}'\n", after: comment
  48. end
  49. end
  50. end
  51. end
  52. # Adds an autoload statement for the given resource name into +app/lib/my_app_name.rb+
  53. #
  54. # An array of namespaces can optionally be supplied in the arguments. The name and namespaces
  55. # should all be lower cased and underscored.
  56. #
  57. # add_autoload "my_model", namespaces: ["my_namespace", "my_other_namespace"]
  58. 2 def add_autoload(name, options = {})
  59. namespaces = Array(options[:namespaces])
  60. # Remove the app namespace if present, we will add the autoload inside the top-level module block
  61. namespaces.shift if namespaces.first == app_namespace
  62. top_level_file = File.join('app', 'lib', "#{underscored_app_namespace}.rb")
  63. if namespaces.empty?
  64. line = " autoload :#{camelcase(name)}, '#{underscored_app_namespace}/#{name}'\n"
  65. insert_into_file top_level_file, line, after: /module #{Origen.app.namespace}\n/
  66. else
  67. contents = File.read(top_level_file)
  68. regex = "module #{Origen.app.namespace}\s*(#.*)?\n"
  69. indent = ''
  70. namespaces.each do |namespace|
  71. indent += ' '
  72. new_regex = regex + "(\n|.)*^\s*module #{camelcase(namespace)}\s*(#.*)?\n"
  73. unless contents =~ Regexp.new(new_regex)
  74. lines = "#{indent}module #{camelcase(namespace)}\n"
  75. lines << "#{indent}end\n"
  76. insert_into_file top_level_file, lines, after: Regexp.new(regex), force: true
  77. end
  78. regex = new_regex
  79. end
  80. line = "#{indent} autoload :#{camelcase(name)}, '#{underscored_app_namespace}/#{namespaces.join('/')}/#{name}'\n"
  81. insert_into_file top_level_file, line, after: Regexp.new(regex)
  82. end
  83. end
  84. # Removes (comments out) the specified configuration setting from +config/application.rb+
  85. #
  86. # comment_config :semantically_version
  87. 2 def comment_config(name, options = {})
  88. # Set the message to be shown in logs
  89. log :comment, name
  90. file = File.join(Origen.root, 'config', 'application.rb')
  91. comment_lines(file, /^\s*config.#{name}\s*=.*\n/)
  92. end
  93. # Adds an entry into +config/application.rb+
  94. 2 def add_config(name, value, options = {})
  95. # Set the message to be shown in logs
  96. message = name.to_s
  97. if value ||= options.delete(:value)
  98. message << " (#{value})"
  99. end
  100. log :insert, message
  101. file = File.join(Origen.root, 'config', 'application.rb')
  102. value = quote(value) if value.is_a?(String)
  103. value = ":#{value}" if value.is_a?(Symbol)
  104. insert_into_file file, " config.#{name} = #{value}\n\n", after: /^\s*class.*\n/
  105. end
  106. # Adds an entry into +Gemfile+ for the supplied gem.
  107. #
  108. # gem "rspec", group: :test
  109. # gem "technoweenie-restful-authentication", lib: "restful-authentication", source: "http://gems.github.com/"
  110. # gem "rails", "3.0", git: "git://github.com/rails/rails"
  111. 2 def gem(name, version, options = {})
  112. # Set the message to be shown in logs. Uses the git repo if one is given,
  113. # otherwise use name (version).
  114. parts, message = [quote(name)], name
  115. if version ||= options.delete(:version)
  116. parts << quote(version)
  117. message << " (#{version})"
  118. end
  119. message = options[:git] if options[:git]
  120. log :gemfile, message
  121. options.each do |option, value|
  122. parts << "#{option}: #{quote(value)}"
  123. end
  124. in_root do
  125. str = "gem #{parts.join(', ')}"
  126. str = ' ' + str if @in_group
  127. str = "\n" + str
  128. append_file 'Gemfile', str, verbose: false
  129. end
  130. end
  131. # Wraps gem entries inside a group.
  132. #
  133. # gem_group :development, :test do
  134. # gem "rspec-rails"
  135. # end
  136. 2 def gem_group(*names, &block)
  137. name = names.map(&:inspect).join(', ')
  138. log :gemfile, "group #{name}"
  139. in_root do
  140. append_file 'Gemfile', "\ngroup #{name} do", force: true
  141. @in_group = true
  142. instance_eval(&block)
  143. @in_group = false
  144. append_file 'Gemfile', "\nend\n", force: true
  145. end
  146. end
  147. # Add the given source to +Gemfile+
  148. #
  149. # add_source "http://gems.github.com/"
  150. 2 def add_source(source, _options = {})
  151. log :source, source
  152. in_root do
  153. prepend_file 'Gemfile', "source #{quote(source)}\n", verbose: false
  154. end
  155. end
  156. # Adds a line inside the Application class for <tt>config/application.rb</tt>.
  157. #
  158. # If options <tt>:env</tt> is specified, the line is appended to the corresponding
  159. # file in <tt>config/environments</tt>.
  160. #
  161. # environment do
  162. # "config.autoload_paths += %W(#{config.root}/extras)"
  163. # end
  164. #
  165. # environment(nil, env: "development") do
  166. # "config.autoload_paths += %W(#{config.root}/extras)"
  167. # end
  168. 2 def environment(data = nil, options = {})
  169. sentinel = /class [a-z_:]+ < Rails::Application/i
  170. env_file_sentinel = /Rails\.application\.configure do/
  171. data = yield if !data && block_given?
  172. in_root do
  173. if options[:env].nil?.map(&:camelcase).join('::')
  174. inject_into_file 'config/application.rb', "\n #{data}", after: sentinel, verbose: false
  175. else
  176. Array(options[:env]).each do |env|
  177. inject_into_file "config/environments/#{env}.rb", "\n #{data}", after: env_file_sentinel, verbose: false
  178. end
  179. end
  180. end
  181. end
  182. 2 alias_method :application, :environment
  183. # Run a command in git.
  184. #
  185. # git :init
  186. # git add: "this.file that.rb"
  187. # git add: "onefile.rb", rm: "badfile.cxx"
  188. 2 def git(commands = {})
  189. if commands.is_a?(Symbol)
  190. run "git #{commands}"
  191. else
  192. commands.each do |cmd, options|
  193. run "git #{cmd} #{options}"
  194. end
  195. end
  196. end
  197. # Create a new file in the lib/ directory. Code can be specified
  198. # in a block or a data string can be given.
  199. #
  200. # lib("crypto.rb") do
  201. # "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
  202. # end
  203. #
  204. # lib("foreign.rb", "# Foreign code is fun")
  205. 2 def lib(filename, data = nil, &block)
  206. log :lib, filename
  207. create_file("lib/#{filename}", data, verbose: false, &block)
  208. end
  209. # Create a new +Rakefile+ with the provided code (either in a block or a string).
  210. #
  211. # rakefile("bootstrap.rake") do
  212. # project = ask("What is the UNIX name of your project?")
  213. #
  214. # <<-TASK
  215. # namespace :#{project} do
  216. # task :bootstrap do
  217. # puts "I like boots!"
  218. # end
  219. # end
  220. # TASK
  221. # end
  222. #
  223. # rakefile('seed.rake', 'puts "Planting seeds"')
  224. 2 def rakefile(filename, data = nil, &block)
  225. log :rakefile, filename
  226. create_file("lib/tasks/#{filename}", data, verbose: false, &block)
  227. end
  228. # Generate something using a generator from Rails or a plugin.
  229. # The second parameter is the argument string that is passed to
  230. # the generator or an Array that is joined.
  231. #
  232. # generate(:authenticated, "user session")
  233. 2 def generate(what, *args)
  234. log :generate, what
  235. argument = args.flat_map(&:to_s).join(' ')
  236. in_root { run_ruby_script("bin/rails generate #{what} #{argument}", verbose: false) }
  237. end
  238. # Reads the given file at the source root and prints it in the console.
  239. #
  240. # readme "README"
  241. 2 def readme(path)
  242. log File.read(find_in_source_paths(path))
  243. end
  244. # Should probably move to its own file, these are general helpers rather than actions
  245. 2 module Helpers
  246. # Returns the depth of the given file, where depth is the number of modules and classes it contains
  247. 2 def internal_depth(file)
  248. depth = 0
  249. File.readlines(file).each do |line|
  250. if line =~ /^\s*(end|def)/
  251. return depth
  252. elsif line =~ /^\s*(module|class)/
  253. depth += 1
  254. end
  255. end
  256. end
  257. # Only executes the given block if the given file does not already define the given method, where the
  258. # block would normally go on to insert the method.
  259. #
  260. # See the ensure_define_sub_blocks method in the sub_blocks.rb generator for a usage example.
  261. 2 def unless_has_method(filepath, name)
  262. unless File.read(filepath) =~ /^\s*def #{name}(\(|\s|\n)/
  263. yield
  264. end
  265. end
  266. # Executes the given block unless the given string is lower cased and underscored and doesn't start
  267. # with a number of contain any special characters
  268. 2 def unless_valid_underscored_identifier(str)
  269. 18 if str =~ /[^0-9a-z_]/ || str =~ /^[0-9]/
  270. yield
  271. end
  272. end
  273. 2 def validate_resource_path(name)
  274. 15 name.split('/').each do |n|
  275. 18 unless_valid_underscored_identifier(n) do
  276. Origen.log.error "All parts of a resource name must be lower-cased, underscored and start with letter, '#{n}' is invalid"
  277. exit 1
  278. end
  279. end
  280. 15 name
  281. end
  282. 2 alias_method :validate_resource_name, :validate_resource_path
  283. # Converts a path to a resource identifier, by performing the following operations on the given path:
  284. # 1) Convert any absolute paths to relative
  285. # 2) Removes any leading blocks/, lib/ or application namespaces
  286. # 3) Remove any derivatives directories from the path
  287. # 3) Removes any trailing .rb
  288. #
  289. # Examples:
  290. #
  291. # /my/code/my_app/app/blocks/dut/derivatives/falcon => dut/falcon
  292. # app/lib/my_app/eagle.rb => eagle
  293. 2 def resource_path(path)
  294. 69 path = Pathname.new(path).expand_path.relative_path_from(Pathname.pwd).to_s
  295. 69 path = path.sub('.rb', '')
  296. 69 path = path.split('/')
  297. 69 from_block_dir_path = false
  298. 69 path.shift if path.first == 'app'
  299. 69 path.shift if path.first == 'lib'
  300. 69 if path.first == 'blocks'
  301. 23 path.shift
  302. 23 from_block_dir_path = true
  303. end
  304. 69 path.shift if path.first == underscored_app_namespace
  305. 69 if path.include?('derivatives')
  306. 15 path.delete('derivatives')
  307. 15 from_block_dir_path = true
  308. end
  309. 69 if from_block_dir_path
  310. 23 path.delete('sub_blocks')
  311. 23 path.pop if path.last == 'model'
  312. 23 if path.last == 'controller'
  313. 2 path.pop
  314. 2 path << "#{path.pop}_controller"
  315. end
  316. end
  317. 69 path.join('/')
  318. end
  319. # Returns a Pathname to the blocks directory that should contain the given class name. No checking is
  320. # done of the name and it is assumed that it is a valid class name including the application namespace.
  321. 2 def class_name_to_blocks_dir(name)
  322. 1 name = name.split('::')
  323. 1 name.shift # Drop the application name
  324. 1 dir = Origen.root.join('app', 'blocks')
  325. 1 name.each_with_index do |n, i|
  326. 1 if i == 0
  327. 1 dir = dir.join(n.underscore)
  328. else
  329. dir = dir.join('derivatives', n.underscore)
  330. end
  331. end
  332. 1 dir
  333. end
  334. # Returns a Pathname to the lib directory file that should contain the given class name. No checking is
  335. # done of the name and it is assumed that it is a valid class name including the application namespace.
  336. 2 def class_name_to_lib_file(name)
  337. 3 name = name.split('::')
  338. 3 dir = Origen.root.join('app', 'lib')
  339. 3 name.each_with_index do |n, i|
  340. 6 dir = dir.join(i == name.size - 1 ? "#{n.underscore}.rb" : n.underscore)
  341. end
  342. 3 dir
  343. end
  344. 2 def resource_path_to_blocks_dir(path)
  345. 34 name = resource_path(path).split('/') # Ensure this is clean, don't care about performance here
  346. 34 dir = Origen.root.join('app', 'blocks')
  347. 34 name.each_with_index do |n, i|
  348. 73 if i == 0
  349. 34 dir = dir.join(n.underscore)
  350. else
  351. 39 if dir.join('sub_blocks', n.underscore).exist?
  352. 13 dir = dir.join('sub_blocks', n.underscore)
  353. else
  354. 26 dir = dir.join('derivatives', n.underscore)
  355. end
  356. end
  357. end
  358. 34 dir
  359. end
  360. 2 def resource_path_to_lib_file(path)
  361. 4 name = resource_path(path).split('/') # Ensure this is clean, don't care about performance here
  362. 4 dir = Origen.root.join('app', 'lib', underscored_app_namespace)
  363. 4 name.each_with_index do |n, i|
  364. 4 dir = dir.join(i == name.size - 1 ? "#{n.underscore}.rb" : n.underscore)
  365. end
  366. 4 dir
  367. end
  368. 2 def resource_path_to_class(path)
  369. 18 name = resource_path(path).split('/') # Ensure this is clean, don't care about performance here
  370. 18 name.unshift(underscored_app_namespace)
  371. 61 name.map { |n| camelcase(n) }.join('::')
  372. end
  373. # Adds :class and :module identifiers to an array of namespaces
  374. #
  375. # ["my_app", "models", "bist"] => [[:module, "my_app"], [:module, "models"], [:class, "bist"]]
  376. #
  377. 2 def add_type_to_namespaces(namespaces)
  378. 10 identifier = nil
  379. 10 namespaces.map do |namespace|
  380. 22 if identifier
  381. 12 identifier += "::#{camelcase(namespace)}"
  382. else
  383. 10 identifier = camelcase(namespace)
  384. end
  385. begin
  386. 22 const = identifier.constantize
  387. 22 [const.is_a?(Class) ? :class : :module, namespace]
  388. rescue NameError
  389. [:module, namespace]
  390. end
  391. end
  392. end
  393. end
  394. 2 include Helpers
  395. 2 protected
  396. # Define log for backwards compatibility. If just one argument is sent,
  397. # invoke say, otherwise invoke say_status. Differently from say and
  398. # similarly to say_status, this method respects the quiet? option given.
  399. 2 def log(*args)
  400. if args.size == 1
  401. say args.first.to_s unless options.quiet?
  402. else
  403. args << (behavior == :invoke ? :green : :red)
  404. say_status(*args)
  405. end
  406. end
  407. 2 def in_root
  408. Dir.chdir(Origen.root) do
  409. yield
  410. end
  411. end
  412. # Surround string with single quotes if there are no quotes,
  413. # otherwise fall back to double quotes
  414. 2 def quote(value)
  415. return value.inspect unless value.is_a? String
  416. if value.include?("'")
  417. value.inspect
  418. else
  419. "'#{value}'"
  420. end
  421. end
  422. end
  423. end
  424. end

lib/origen/code_generators/base.rb

94.12% lines covered

34 relevant lines. 32 lines covered and 2 lines missed.
    
  1. 2 require 'thor/group'
  2. 2 module Origen
  3. 2 module CodeGenerators
  4. 2 class Error < Thor::Error # :nodoc:
  5. end
  6. 2 class Base < Thor::Group
  7. 2 include Thor::Actions
  8. 2 include Origen::CodeGenerators::Actions
  9. 2 add_runtime_options!
  10. 2 strict_args_position!
  11. # Convenience method to get the top-level namespace from the class name.
  12. # It is returned as a lower cased and underscored string.
  13. 2 def self.namespace(name = nil)
  14. 60 @namespace ||= begin
  15. 24 names = super.split(':')
  16. 24 if names.size == 1
  17. nil
  18. else
  19. 24 names.first.sub(/^r_gen/, 'origen')
  20. end
  21. end
  22. end
  23. # Sets the base_name taking into account the current class namespace.
  24. 2 def self.name
  25. 72 @name ||= begin
  26. 24 name = to_s.split('::').last.sub(/(CodeGenerator|Generator)$/, '').underscore
  27. 24 if name == 'klass'
  28. 1 'class'
  29. 23 elsif name == 'mod'
  30. 1 'module'
  31. else
  32. 22 name
  33. end
  34. end
  35. end
  36. # Cache source root and add lib/generators/base/generator/templates to
  37. # source paths.
  38. 2 def self.inherited(base) #:nodoc:
  39. 24 super
  40. 24 if base.name && base.name !~ /Base$/
  41. 24 if base.namespace == 'origen'
  42. 6 Origen::CodeGenerators.origen_generators[base.name] = base
  43. else
  44. 18 Origen::CodeGenerators.plugin_generators[base.namespace] ||= {}
  45. 18 Origen::CodeGenerators.plugin_generators[base.namespace][base.name] = base
  46. end
  47. end
  48. # Give all generators access to Origen core files in their source path,
  49. # with their own app as highest priority
  50. 24 base.source_paths << Origen.root if Origen.app_loaded?
  51. 24 base.source_paths << Origen.top
  52. end
  53. 2 def self.banner
  54. "origen new #{namespace == 'origen' ? '' : namespace + ':'}#{name} [options]"
  55. end
  56. end
  57. end
  58. end

lib/origen/code_generators/block.rb

76.86% lines covered

121 relevant lines. 93 lines covered and 28 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. 1 class Block < Origen::CodeGenerators::Base
  4. 1 include BlockCommon
  5. # class_option :duts, type: :boolean, desc: 'Instantiate the new sub-block in all DUT models', default: true
  6. # class_option :instance, desc: 'The main NAME argument will be the name given to the model and the instantiated sub-block, optionally provide a different name for the instance'
  7. 1 def self.banner
  8. 'origen new block [TYPE/]DERIVATIVE [BLOCK]'
  9. end
  10. 1 desc <<-END
  11. This generator creates a block (e.g. to represent RAM, ATD, Flash, DAC, etc.) and all of the associated
  12. resources for it, e.g. a model, controller, timesets, parameters, etc.
  13. The TYPE and DERIVATIVE names should be given in lower case (e.g. flash/flash2kb, atd/atd16), optionally with
  14. additional parent sub-block names after the initial type.
  15. Alternatively, a reference to an existing BLOCK can be added, in which case a nested block will be created
  16. within that block's sub_blocks directory, rather than a primary block.
  17. Note that nested blocks do not support derivatives or inheritance and should therefore only be used for
  18. relatively simple entities which are tightly coupled to a parent block.
  19. Any parent block(s) will be created if they don't exist, but they will not be modified if they do.
  20. Examples:
  21. origen new block atd/atd8bit # Creates app/blocks/atd/derivatives/atd8bit/...
  22. origen new block atd/atd16bit # Creates app/blocks/atd/derivatives/atd16bit/...
  23. origen new block nvm/flash/flash2kb # Creates app/blocks/nvm/derivatives/flash/derivatives/flash2kb/...
  24. # Example of creating a nested sub-block
  25. origen new block nvm/flash/flash2kb bist # Creates app/blocks/nvm/derivatives/flash/derivatives/flash2kb/sub_blocks/bist/...
  26. END
  27. 1 def validate_args
  28. 5 if args.size > 2 || args.size == 0
  29. msg = args.size == 0 ? 'At least one argument is' : 'No more than two arguments are'
  30. msg << " expected by the block generator, e.g. 'origen new block atd/atd16bit', 'origen new block sampler app/blocks/atd/derivatives/atd16bit"
  31. puts msg
  32. exit 1
  33. end
  34. 5 if args.size == 2
  35. 3 validate_args_common(args.last)
  36. else
  37. 2 validate_args_common
  38. end
  39. 5 @nested = args.size == 2
  40. 5 if !@nested && args.first.split('/').size == 1
  41. msg = "You must supply a leading type to the name of the block, e.g. 'origen new block atd/atd16bit'"
  42. puts msg
  43. exit 1
  44. end
  45. 5 if @nested && args.last.split('/').size != 1
  46. msg = "No leading type is allowed when generating a nested block, e.g. 'origen new block sampler app/blocks/atd/derivatives/atd16bit"
  47. puts msg
  48. exit 1
  49. end
  50. end
  51. 1 def setup
  52. 5 @generate_model = true
  53. 5 @generate_pins = false
  54. 5 @generate_timesets = !@nested
  55. 5 @generate_parameters = !@nested
  56. 5 if @nested
  57. 3 @final_name = args.last
  58. 3 @fullname = resource_path_to_class(args.first)
  59. 3 @dir = resource_path_to_blocks_dir(args.first).join('sub_blocks', @final_name)
  60. 3 @namespaces = add_type_to_namespaces(@fullname.split('::').map(&:underscore))
  61. else
  62. 2 extract_model_name
  63. end
  64. 5 create_files
  65. end
  66. 1 def instantiate_sub_block
  67. 5 if @nested
  68. # First create the parent's sub_blocks.rb file if it doesn't exist
  69. 3 f = "#{@dir.parent}.rb"
  70. 3 unless File.exist?(f)
  71. @nested = false
  72. orig_fullname = @fullname
  73. orig_resouce_path = @resource_path
  74. @fullname = @fullname.split('::')
  75. @fullname.pop
  76. @fullname = @fullname.join('::')
  77. @resource_path = @resource_path.split('/')
  78. @resource_path.pop
  79. @resource_path = @resource_path.join('/')
  80. template 'templates/code_generators/sub_blocks.rb', f
  81. @fullname = orig_fullname
  82. @resource_path = orig_resouce_path
  83. @nested = true
  84. end
  85. 3 line = "sub_block :#{@final_name}, class_name: '#{@fullname}'#, base_address: 0x4000_0000"
  86. 3 append_to_file f, "\n#{line}"
  87. else
  88. 2 @line = "sub_block :#{@final_namespaces[1]}, class_name: '#{class_name}'#, base_address: 0x4000_0000"
  89. 2 unless duts.empty?
  90. 2 puts
  91. 2 @dut_index = [nil]
  92. 2 index = 1
  93. 2 duts.each do |name, children|
  94. 2 index = print_dut(name, index, children, 0)
  95. end
  96. 2 puts
  97. 2 puts 'DO YOU WANT TO INSTANTIATE THIS SUB-BLOCK IN YOUR DUT MODELS?'
  98. 2 puts
  99. 2 puts 'If so enter the number(s) of the DUT(s) you wish to add it to from the list above, separating multiple entries with a space'
  100. 2 puts '(note that adding it to a parent DUT in the hierarchy will already be adding it to all of its children).'
  101. 2 puts
  102. 2 response = ask 'Enter the DUT number(s), or just press return to skip:'
  103. 2 done = []
  104. 2 response.strip.split(/\s+/).each do |index|
  105. 1 index = index.to_i
  106. 1 target = @dut_index[index]
  107. 1 if target
  108. # Don't add the sub-block to children if we've already added it to the parent, this will
  109. # cause an already defined sub-block error since it will be added by both instantiations
  110. 1 unless done.any? { |c| target =~ /^#{c}::/ }
  111. 1 done << target
  112. 1 sub_blocks = class_name_to_blocks_dir(target).join('sub_blocks.rb')
  113. 1 unless sub_blocks.exist?
  114. orig = @fullname
  115. @fullname = target
  116. template 'templates/code_generators/sub_blocks.rb', sub_blocks
  117. @fullname = orig
  118. end
  119. 1 @sub_block_instantiated = true
  120. 1 append_to_file sub_blocks, "\n#{@line}"
  121. end
  122. end
  123. end
  124. end
  125. end
  126. end
  127. 1 def completed
  128. 5 add_acronyms
  129. 5 puts
  130. 5 if @nested
  131. 3 puts 'New sub-block created and instantiated.'.green
  132. else
  133. 2 if @sub_block_instantiated
  134. 1 puts 'New sub-block created and instantiated within your DUT(s) as:'.green + " dut.#{@final_namespaces[1]}"
  135. else
  136. 1 puts 'New sub-block created, you can instantiate it within your blocks like this:'.green
  137. 1 puts
  138. 1 puts " #{@line}"
  139. end
  140. end
  141. 5 puts
  142. end
  143. 1 private
  144. 1 def print_dut(name, index, children, offset)
  145. 8 @dut_index << name
  146. 8 puts "#{index}".ljust(2) + ': ' + (' ' * offset) + name
  147. 8 index += 1
  148. 8 children.each do |name, children|
  149. 6 index = print_dut(name, index, children, offset + 1)
  150. end
  151. 8 index
  152. end
  153. # Returns a look up table for all dut blocks defined in this application (only those defined
  154. # as blocks, as they all should be now).
  155. # This is arranged by hierarchy.
  156. 1 def duts
  157. 4 @duts ||= begin
  158. 2 duts = {}
  159. 2 dut_dir = Pathname.new(File.join(Origen.root, 'app', 'blocks', 'dut'))
  160. 2 if dut_dir.exist?
  161. 2 name = "#{Origen.app.namespace}::DUT"
  162. 2 duts[name] = {}
  163. 2 add_derivatives(duts[name], name, dut_dir)
  164. end
  165. 2 duts
  166. end
  167. end
  168. 1 def add_derivatives(duts, name, dir)
  169. 8 derivatives = dir.join('derivatives')
  170. 8 if derivatives.exist?
  171. 4 derivatives.children.each do |item|
  172. 6 if item.directory?
  173. 6 child_name = "#{name}::#{camelcase(item.basename)}"
  174. 6 duts[child_name] = {}
  175. 6 add_derivatives(duts[child_name], child_name, item)
  176. end
  177. end
  178. end
  179. end
  180. end
  181. end
  182. end

lib/origen/code_generators/block_common.rb

100.0% lines covered

67 relevant lines. 67 lines covered and 0 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. # Base generator for the DUT, block and feature generators
  4. 1 module BlockCommon
  5. 1 def validate_args_common(arg = nil)
  6. 8 validate_resource_name(arg || args.first)
  7. end
  8. 1 def extract_model_name
  9. 5 @final_namespaces = args.first.downcase.split('/')
  10. 5 @final_name = @final_namespaces.pop
  11. 5 @final_name.gsub!(/\.rb/, '')
  12. 5 @final_namespaces.unshift('dut') if @top_level
  13. 5 @final_namespaces.unshift(underscored_app_namespace)
  14. 5 @model_path = @final_namespaces.dup
  15. 5 @namespaces = [[:module, @model_path.shift]]
  16. end
  17. 1 def create_files
  18. # @summary = ask 'Describe your plugin in a few words:'
  19. 8 @block = true
  20. 8 @root_class = true
  21. # Nested sub-blocks do not support inheritance
  22. 8 unless @nested
  23. 5 dir = File.join(Origen.root, 'app', 'blocks')
  24. 5 @fullname = Origen.app.namespace.to_s
  25. 5 @model_path.each do |path|
  26. 5 dir = File.join(dir, path)
  27. 5 @name = path
  28. 5 @fullname += "::#{camelcase(@name)}"
  29. 5 @resource_path = resource_path(dir)
  30. 5 if @generate_model
  31. 5 f = File.join(dir, 'model.rb')
  32. 5 template 'templates/code_generators/model.rb', f unless File.exist?(f)
  33. 5 f = File.join(dir, 'controller.rb')
  34. 5 template 'templates/code_generators/controller.rb', f unless File.exist?(f)
  35. end
  36. 5 if @generate_pins
  37. 3 f = File.join(dir, 'pins.rb')
  38. 3 template 'templates/code_generators/pins.rb', f unless File.exist?(f)
  39. end
  40. 5 if @generate_timesets
  41. 5 f = File.join(dir, 'timesets.rb')
  42. 5 template 'templates/code_generators/timesets.rb', f unless File.exist?(f)
  43. end
  44. 5 if @generate_parameters
  45. 5 f = File.join(dir, 'parameters.rb')
  46. 5 template 'templates/code_generators/parameters.rb', f unless File.exist?(f)
  47. end
  48. 5 f = File.join(dir, 'registers.rb')
  49. 5 template 'templates/code_generators/registers.rb', f unless File.exist?(f)
  50. 5 f = File.join(dir, 'sub_blocks.rb')
  51. 5 template 'templates/code_generators/sub_blocks.rb', f unless File.exist?(f)
  52. 5 f = File.join(dir, 'attributes.rb')
  53. 5 template 'templates/code_generators/attributes.rb', f unless File.exist?(f)
  54. 5 dir = File.join(dir, 'derivatives')
  55. 5 @namespaces << [:class, path]
  56. 5 @root_class = false
  57. 16 @parent_class = @namespaces.map { |type, name| camelcase(name) }.join('::')
  58. end
  59. 6 @parent_class ||= @namespaces.map { |type, name| camelcase(name) }.join('::')
  60. end
  61. 8 @name = @final_name
  62. 8 @fullname += "::#{camelcase(@name)}"
  63. 8 dir = @dir || File.join(dir, @name)
  64. 8 @resource_path = resource_path(dir)
  65. 8 if @generate_model
  66. 7 template 'templates/code_generators/model.rb', File.join(dir, 'model.rb')
  67. 7 template 'templates/code_generators/controller.rb', File.join(dir, 'controller.rb')
  68. end
  69. 8 if @generate_pins
  70. 3 template 'templates/code_generators/pins.rb', File.join(dir, 'pins.rb')
  71. end
  72. 8 if @generate_timesets
  73. 5 template 'templates/code_generators/timesets.rb', File.join(dir, 'timesets.rb')
  74. end
  75. 8 if @generate_parameters
  76. 5 template 'templates/code_generators/parameters.rb', File.join(dir, 'parameters.rb')
  77. end
  78. 8 template 'templates/code_generators/registers.rb', File.join(dir, 'registers.rb')
  79. 8 template 'templates/code_generators/sub_blocks.rb', File.join(dir, 'sub_blocks.rb')
  80. 8 template 'templates/code_generators/attributes.rb', File.join(dir, 'attributes.rb')
  81. end
  82. 1 def class_name
  83. 8 (@final_namespaces + Array(@name)).map { |n| camelcase(n) }.join('::')
  84. end
  85. end
  86. end
  87. end

lib/origen/code_generators/dut.rb

85.29% lines covered

34 relevant lines. 29 lines covered and 5 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. 1 class Dut < Origen::CodeGenerators::Base
  4. 1 include BlockCommon
  5. 1 def self.banner
  6. 'origen new dut NAME'
  7. end
  8. 1 desc <<-END
  9. This generator creates a top-level (DUT) block and all of the associated resources for it, e.g. a model,
  10. controller, target, timesets, pins, etc.
  11. The NAME of the DUT should be given in lower case, optionally prefixed by parent DUT name(s) separated
  12. by a forward slash.
  13. Any parent DUT(s) will be created if they don't exist, but they will not be modified if they do.
  14. Examples:
  15. origen new dut falcon # Creates app/blocks/dut/derivatives/falcon/...
  16. origen new dut dsp/falcon # Creates app/blocks/dut/derivatives/dsp/derivatives/falcon/...
  17. END
  18. 1 def validate_args
  19. 2 if args.size > 1 || args.size == 0
  20. msg = args.size > 1 ? 'Only one' : 'One'
  21. msg << " argument is expected by the DUT generator, e.g. 'origen new dut my_soc', 'origen new dut my_family/my_soc"
  22. puts msg
  23. exit 1
  24. end
  25. 2 validate_args_common
  26. end
  27. 1 def setup
  28. 2 @generate_model = true
  29. 2 @generate_pins = true
  30. 2 @generate_timesets = true
  31. 2 @generate_parameters = true
  32. 2 @top_level = true
  33. 2 extract_model_name
  34. 2 create_files
  35. end
  36. 1 def create_target
  37. 2 contents = ''
  38. 7 contents << @final_namespaces.map { |n| camelcase(n) }.join('::')
  39. 2 contents << "::#{camelcase(@name)}.new\n"
  40. 2 create_file "#{Origen.root}/target/#{@name}.rb", contents
  41. end
  42. 1 def completed
  43. 2 add_acronyms
  44. 2 puts
  45. 2 puts 'New DUT created, run the following command to target it in your workspace:'.green
  46. 2 puts
  47. 2 puts " origen t #{@name}"
  48. 2 puts
  49. end
  50. end
  51. end
  52. end

lib/origen/code_generators/feature.rb

77.27% lines covered

22 relevant lines. 17 lines covered and 5 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. 1 class Feature < Origen::CodeGenerators::Base
  4. 1 include BlockCommon
  5. 1 def self.banner
  6. 'origen new feature NAME'
  7. end
  8. 1 desc <<-END
  9. This generator creates a new feature block, which is similar to a regular block but with no model and controller.
  10. Such features can then be loaded (re-used) by multiple blocks within your application code.
  11. The name of the feature should be given in lower case, optionally prefixed by parent feature name(s) separated
  12. by a forward slash.
  13. Any parent features will be created if they don't exist, but they will not be modified if they do.
  14. Examples:
  15. origen new feature my_feature # Creates app/blocks/my_feature/...
  16. origen new feature features/my_feature # Creates app/blocks/features/my_feature/...
  17. The above can then be loaded to models in your application code via:
  18. my_model.load_block('my_feature')
  19. my_model.load_block('features/my_feature')
  20. END
  21. 1 def validate_args
  22. 1 if args.size > 1 || args.size == 0
  23. msg = args.size > 1 ? 'Only one' : 'One'
  24. msg << " argument is expected by the feature generator, e.g. 'origen new feature my_feature', 'origen new feature features/my_feature"
  25. puts msg
  26. exit 1
  27. end
  28. 1 validate_args_common
  29. end
  30. 1 def setup
  31. 1 @generate_model = false
  32. 1 @generate_pins = true
  33. 1 @generate_timesets = true
  34. 1 @generate_parameters = true
  35. 1 extract_model_name
  36. 1 create_files
  37. 1 add_acronyms
  38. end
  39. end
  40. end
  41. end

lib/origen/code_generators/klass.rb

77.27% lines covered

22 relevant lines. 17 lines covered and 5 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. 1 class Klass < Origen::CodeGenerators::Base
  4. 1 def self.banner
  5. 'origen new class NAME'
  6. end
  7. 1 desc <<-END
  8. This generator creates a plain old Ruby class within your application's lib directory.
  9. The NAME of the class should be given, in lower case, optionally indicating the presence
  10. of any namespacing you want it to be created under.
  11. Examples:
  12. origen new class counter # Creates app/lib/my_application/counter.rb
  13. origen new class helpers/counter # Creates app/lib/my_application/helpers/counter.rb
  14. END
  15. 1 def validate_args
  16. 1 if args.size > 1 || args.size == 0
  17. msg = args.size > 1 ? 'Only one' : 'One'
  18. msg << " argument is expected by the class generator, e.g. 'origen new class counter', 'origen new class helpers/counter'"
  19. puts msg
  20. exit 1
  21. end
  22. 1 validate_resource_name(args.first)
  23. end
  24. 1 def create_class_file
  25. 1 @resource_path = args.first
  26. 1 klass = resource_path_to_class(args.first)
  27. 1 @namespaces = klass.split('::').map(&:underscore)
  28. 1 @name = @namespaces.pop
  29. 1 @namespaces = add_type_to_namespaces(@namespaces)
  30. 1 @root_class = true
  31. 1 file = class_name_to_lib_file(klass)
  32. 1 template 'templates/code_generators/class.rb', file
  33. end
  34. end
  35. end
  36. end

lib/origen/code_generators/model.rb

80.77% lines covered

26 relevant lines. 21 lines covered and 5 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. 1 class Model < Origen::CodeGenerators::Base
  4. 1 def self.banner
  5. 'origen new model NAME'
  6. end
  7. 1 desc <<-END
  8. This generator creates a model and optionally a controller for it within your application's
  9. app/lib directory.
  10. The NAME of the model should be given, in lower case, optionally indicating the presence
  11. of any namespacing you want it to be created under.
  12. If the model is intended to represent a top-level DUT or a primary sub-block/IP (e.g. RAM,
  13. ATD, PLL, Flash, etc) then use `origen new dut` or `origen new block` instead.
  14. If the model is intended to represent a sub-component of an existing block then the
  15. block generator should be used to create a nested sub-block - see the comments within
  16. sub_blocks.rb of one of the existing block models for an example.
  17. Otherwise, models in the app/lib directory as produced by this generator are good for when
  18. the model is representing some abstract concept which may not map directly to hardware, or
  19. hen you need to model a minor sub-component which needs to be shared by multuple higher level
  20. blocks.
  21. Examples:
  22. origen new model sequencer # Creates app/lib/my_application/sequencer.rb
  23. origen new model bist/sequencer # Creates app/lib/my_application/bist/sequencer.rb
  24. END
  25. 1 def validate_args
  26. 2 if args.size > 1 || args.size == 0
  27. msg = args.size > 1 ? 'Only one' : 'One'
  28. msg << " argument is expected by the model generator, e.g. 'origen new model sequencer', 'origen new model bist/sequencer'"
  29. puts msg
  30. exit 1
  31. end
  32. 2 validate_resource_name(args.first)
  33. end
  34. 1 def create_model_file
  35. 2 @resource_path = args.first
  36. 2 klass = resource_path_to_class(args.first)
  37. 2 @namespaces = klass.split('::').map(&:underscore)
  38. 2 @name = @namespaces.pop
  39. 2 @namespaces = add_type_to_namespaces(@namespaces)
  40. 2 @root_class = true
  41. 2 file = class_name_to_lib_file(klass)
  42. 2 template 'templates/code_generators/model.rb', file
  43. 2 if yes? 'Does this model need a controller? (n):'
  44. 1 file = file.to_s.sub(/\.rb/, '_controller.rb')
  45. 1 template 'templates/code_generators/controller.rb', file
  46. end
  47. 2 add_acronyms
  48. end
  49. end
  50. end
  51. end

lib/origen/code_generators/module.rb

66.67% lines covered

48 relevant lines. 32 lines covered and 16 lines missed.
    
  1. 1 module Origen
  2. 1 module CodeGenerators
  3. 1 class Mod < Origen::CodeGenerators::Base
  4. 1 def self.banner
  5. 'origen new module NAME [CLASS]'
  6. end
  7. 1 desc <<-END
  8. This generator creates a plain old Ruby module within your application's lib directory,
  9. or if a CLASS argument is given, it will create it a child of that class in either the
  10. lib or blocks directory as appropriate.
  11. Where a CLASS argument is given, the new module will be automatically included in the
  12. class.
  13. The NAME of the module should be given, in lower case, optionally indicating the presence
  14. of any namespacing you want it to be created under.
  15. The CLASS argument should be a path to the Ruby file that defines the class.
  16. Examples:
  17. origen new module helpers # Creates app/lib/my_application/helpers.rb
  18. origen new module helpers/math # Creates app/lib/my_application/helpers/math.rb
  19. # Creates app/lib/blocks/dut/derivatives/falcon/model/helpers.rb
  20. origen new module blocks/dut/derivatives/falcon/model.rb helpers
  21. END
  22. 1 def validate_args
  23. 4 if args.size > 2 || args.size == 0
  24. msg = args.size == 0 ? 'At least one argument is' : 'No more than two arguments are'
  25. msg << " expected by the module generator, e.g. 'origen new module helpers', 'origen new module helpers app/lib/my_app/my_class.rb'"
  26. puts msg
  27. exit 1
  28. end
  29. 4 if args.size == 2
  30. 4 @class_file = args.first
  31. 4 unless File.exist?(@class_file)
  32. puts "This class file does not exist: #{@class_file}"
  33. exit 1
  34. end
  35. end
  36. 4 @resource_path = validate_resource_path(args.last)
  37. end
  38. 1 def create_module_file
  39. 4 if @class_file
  40. 4 @namespaces = resource_path_to_class(@class_file).split('::').map(&:underscore)
  41. 4 paths = resource_path_to_class(@resource_path).split('::').map(&:underscore)
  42. 4 @name = paths.pop
  43. 4 paths.shift # Lose the app namespace
  44. 4 @namespaces += paths
  45. 4 file = File.join(@class_file.sub('.rb', ''), "#{@name}.rb")
  46. 18 @module_name = (@namespaces + [@name]).map { |n| camelcase(n) }.join('::')
  47. else
  48. @module_name = resource_path_to_class(@resource_path)
  49. @namespaces = @module_name.split('::').map(&:underscore)
  50. @name = @namespaces.pop
  51. file = class_name_to_lib_file(@module_name)
  52. end
  53. 4 @namespaces = add_type_to_namespaces(@namespaces)
  54. 4 template 'templates/code_generators/module.rb', file
  55. end
  56. 1 def include_module
  57. 4 if @class_file
  58. 4 klass = resource_path_to_class(@class_file)
  59. # Does file have a nested namespace structure
  60. 4 snippet = File.foreach(@class_file).first(50)
  61. 83 if snippet.any? { |line| line =~ /\s*class #{klass.split('::').last}/ }
  62. indent = ' ' * klass.split('::').size
  63. lines = []
  64. lines << indent + "include #{@module_name}"
  65. lines << ''
  66. inject_into_class @class_file, klass.split('::').last, lines.join("\n") + "\n"
  67. # Else assume it is the compact style (class MyApp::DUT::Falcon)
  68. else
  69. 4 lines = []
  70. 4 lines << " include #{@module_name}"
  71. 4 lines << ''
  72. 4 inject_into_class @class_file, klass, lines.join("\n") + "\n"
  73. end
  74. end
  75. 4 add_acronyms
  76. end
  77. end
  78. end
  79. end

lib/origen/commands/compile.rb

100.0% lines covered

31 relevant lines. 31 lines covered and 0 lines missed.
    
  1. 1 require 'optparse'
  2. 1 require 'origen/commands/helpers'
  3. 1 options = {}
  4. # App options are options that the application can supply to extend this command
  5. 1 app_options = @application_options || []
  6. 1 opt_parser = OptionParser.new do |opts|
  7. 1 opts.banner = <<-EOT
  8. Compile an ERB template file, list of files or a directory.
  9. If a directory is referenced the entire sub-directory structure will be copied to the output directory, compiling any ERB
  10. templates it finds in the process and copying any standard files accross without modification.
  11. ERB is a templating system from Ruby that allows you to embed snippets of Ruby code in any ASCII file to create dynamic
  12. content. Within the context of the origen generator this means that you can include Ruby snippets that reference the target
  13. objects to create dynamic test program sheets, C files, VB files, documentation, etc.
  14. To create an ERB template start with a base ASCII file and append .erb to the end of the file name, upon compilation the
  15. .erb extension will be removed.
  16. There is not much to the syntax, this snippet covers just about everything you need to know:
  17. % - A full line of Ruby is prefixed with %, this is removed by compilation
  18. %# - A full line Ruby comment, this is removed by compilation
  19. <%# %> - An embedded Ruby comment, this is removed by compilation
  20. <% %> - An embedded Ruby snippet, this is removed by compilation
  21. <%= %> - An embedded Ruby snippet that generates content, the result is output to the compiled file
  22. Full details of ERB syntax can be found here:
  23. http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html
  24. Usage: origen compile [space separated files, lists or directories] [options]
  25. EOT
  26. 1 opts.on('-e', '--environment NAME', String, 'Override the default environment, NAME can be a full path or a fragment of an environment file name') { |e| options[:environment] = e }
  27. 2 opts.on('-t', '--target NAME', String, 'Override the default target, NAME can be a full path or a fragment of a target file name') { |t| options[:target] = t }
  28. 2 opts.on('-pl', '--plugin PLUGIN_NAME', String, 'Set current plugin') { |pl_n| options[:current_plugin] = pl_n }
  29. 1 opts.on('-l', '--lsf [ACTION]', [:clear, :add], "Submit jobs to the LSF, optionally specify whether to 'clear' or 'add' to existing jobs") { |a| options[:lsf] = true; options[:lsf_action] = a }
  30. 1 opts.on('-w', '--wait', 'Wait for LSF processing to complete') { options[:wait_for_lsf_completion] = true }
  31. 1 opts.on('-c', '--continue', 'Continue on error (to the next file)') { options[:continue] = true }
  32. 1 opts.on('-d', '--debugger', 'Enable the debugger') { options[:debugger] = true }
  33. 1 opts.on('-m', '--mode MODE', Origen::Mode::MODES, 'Force the Origen operating mode:', ' ' + Origen::Mode::MODES.join(', ')) { |_m| }
  34. 1 opts.on('-f', '--file FILE', String, 'Override the default log file') { |o| options[:log_file] = o }
  35. 1 opts.on('-o', '--output DIR', String, 'Override the default output directory') { |o| options[:output] = o }
  36. 1 opts.on('-n', '--name NAME', String, 'Override the default output file name') { |o| options[:output_file_name] = o }
  37. 2 opts.on('-r', '--reference DIR', String, 'Override the default reference directory') { |o| options[:reference] = o }
  38. 1 opts.on('-z', '--zip', 'Gzip all output files (diff checking will be skipped)') { |o| options[:zip] = o }
  39. # Apply any application option extensions to the OptionParser
  40. 1 Origen::CommandHelpers.extend_options(opts, app_options, options)
  41. 1 opts.separator ''
  42. 1 opts.on('-h', '--help', 'Show this message') { puts opts; exit 0 }
  43. end
  44. 1 opt_parser.parse! ARGV
  45. 1 options[:patterns] = ARGV
  46. 1 options[:compile] = true # To let the generator know a compile job has been requested
  47. 1 Origen.app.plugins.temporary = options[:current_plugin] if options[:current_plugin]
  48. 1 Origen.environment.temporary = options[:environment] if options[:environment]
  49. 1 Origen.target.temporary = options[:target] if options[:target]
  50. 1 Origen.app.load_target!
  51. 1 Origen.app.runner.generate(options)
  52. 1 Origen.lsf.wait_for_completion if options[:wait_for_lsf_completion]

lib/origen/commands/generate.rb

91.11% lines covered

45 relevant lines. 41 lines covered and 4 lines missed.
    
  1. 1 require 'optparse'
  2. 1 require 'origen/commands/helpers'
  3. 1 options = {}
  4. # App options are options that the application can supply to extend this command
  5. 1 app_options = @application_options || []
  6. 1 opt_parser = OptionParser.new do |opts|
  7. 1 opts.banner = 'Usage: origen g [space separated patterns or lists] [options]'
  8. 1 opts.on('-e', '--environment NAME', String, 'Override the default environment, NAME can be a full path or a fragment of an environment file name') { |e| options[:environment] = e }
  9. 2 opts.on('-t', '--target NAME', String, 'Override the default target, NAME can be a full path or a fragment of a target file name') { |t| options[:target] = t }
  10. 1 opts.on('-l', '--lsf [ACTION]', [:clear, :add], "Submit jobs to the LSF, optionally specify whether to 'clear' or 'add' to existing jobs") { |a| options[:lsf] = true; options[:lsf_action] = a }
  11. 1 opts.on('-w', '--wait', 'Wait for LSF processing to complete') { options[:wait_for_lsf_completion] = true }
  12. 1 opts.on('-c', '--continue', 'Continue on error (to the next pattern)') { options[:continue] = true }
  13. 2 opts.on('-pl', '--plugin PLUGIN_NAME', String, 'Set current plugin') { |pl_n| options[:current_plugin] = pl_n }
  14. 1 opts.on('-f', '--file FILE', String, 'Override the default log file') { |o| options[:log_file] = o }
  15. 1 opts.on('-o', '--output DIR', String, 'Override the default output directory') { |o| options[:output] = o }
  16. 2 opts.on('-r', '--reference DIR', String, 'Override the default reference directory') { |o| options[:reference] = o }
  17. 1 opts.on('-q', '--queue NAME', String, 'Specify the LSF queue, default is short') { |o| options[:queue] = o }
  18. 1 opts.on('-p', '--project NAME', String, 'Specify the LSF project, default is msg.te') { |o| options[:project] = o }
  19. 1 opts.on('--doc', 'Generate into doc format') { options[:doc] = true }
  20. 1 opts.on('--html', 'Generate into html format') { options[:html] = true }
  21. 1 opts.on('--nocom', 'No comments in the generated pattern') { options[:no_comments] = true }
  22. 1 opts.on('-seq', '--sequence NAME', String, 'Generate multiple patterns into a single concurrent pattern sequence') { |o| options[:sequence] = o }
  23. 1 opts.on('-d', '--debugger', 'Enable the debugger') { options[:debugger] = true }
  24. 1 opts.on('-m', '--mode MODE', Origen::Mode::MODES, 'Force the Origen operating mode:', ' ' + Origen::Mode::MODES.join(', ')) { |_m| }
  25. # Apply any application option extensions to the OptionParser
  26. 1 Origen::CommandHelpers.extend_options(opts, app_options, options)
  27. 1 opts.separator ''
  28. 1 opts.on('-h', '--help', 'Show this message') { puts opts; exit }
  29. end
  30. 1 opt_parser.parse! ARGV
  31. 1 options[:patterns] = ARGV
  32. 1 def self._with_doc_tester(options)
  33. 1 if options[:doc] || options[:html]
  34. Origen.app.with_doc_tester(options) do
  35. yield
  36. end
  37. else
  38. 1 yield
  39. end
  40. end
  41. 1 Origen.load_application
  42. 1 if options[:queue]
  43. Origen.config.lsf.queue = options.delete(:queue)
  44. end
  45. 1 if options[:project]
  46. Origen.config.lsf.project = options.delete(:project)
  47. end
  48. 1 _with_doc_tester(options) do
  49. 1 Origen.app.plugins.temporary = options[:current_plugin] if options[:current_plugin]
  50. 1 Origen.environment.temporary = options[:environment] if options[:environment]
  51. 1 Origen.target.temporary = options[:target] if options[:target]
  52. 1 Origen.app.load_target! # This initial load is required to apply any configuration
  53. # options present in the target, it will loaded again before
  54. # each generate/compile job
  55. 1 Origen.app.runner.generate(options)
  56. 1 Origen.lsf.wait_for_completion if options[:wait_for_lsf_completion]
  57. end
  58. # method_option :vector_comments, :default => false, :aliases => "-v", :type => :boolean,
  59. # :desc => "Add vector and cycle number comments to the pattern, disabled by default to make diff viewing easier"
  60. # === Task: $ origen g [patname/patlist] [options]
  61. # Generate a pattern or list of patterns.
  62. #
  63. # ==== Supplying a pattern name
  64. # Multiple pattern name arguments can be supplied on the command line. The generator is non-strict
  65. # about pre and post-fixes to the pattern names so the following all work; use whichever is most
  66. # convenient:
  67. #
  68. # origen g prb_ers_mas_atuf
  69. # origen g prb_ers_mas_atuf.rb
  70. # origen g nvm_prb_ers_mas_atuf.atp
  71. # origen g pattern/erase/prb_ers_mas_atuf.rb
  72. #
  73. # For patterns that use iterators you can supply either the pattern source name or the generated name,
  74. # for example these are equivalent:
  75. #
  76. # origen g prb_ers_mas_bx.rb
  77. # origen g prb_ers_mas_b0.atp
  78. #
  79. # Multiple patterns can be supplied on the command line and should be comma separated with no spaces
  80. # in between:
  81. #
  82. # origen g prb_ers_mas_atuf,prb_ers_mas_bx
  83. #
  84. # A pattern list is simply the name of any file that resides in the /list directory and which contains
  85. # a list of patname arguments (same rules apply as for the command line version).
  86. # The list files can be commented with '#' and can reference other list files. For example it is
  87. # common to make a production.list or master.list file that references other sub lists: probe.list,
  88. # ft.list, etc.
  89. #
  90. # ==== Generator output
  91. # The command line output is as follows:
  92. #
  93. # Generating... nvm_prb_ers_mas_atuf.atp 3039 0.201732
  94. # | | |
  95. # Created output file, can be found in Number of Execution time
  96. # output/<soc>/ vectors on the tester
  97. #
  98. # All output from the last run can be found in log.txt, this is just a direct copy of the output in
  99. # the console.
  100. #
  101. # ==== Tracking changes
  102. # Upon completion you will be alerted if there are some new or changed files and will prompted to
  103. # save these. It is recommended that you perform the save if you know why the change happened (or if
  104. # you are running in a brand new workspace). You can then use the automatic pattern diff feature to
  105. # be alerted to any change in the pattern content the next time you generate a given pattern. Note that
  106. # a tkdiff executable is automatically output to allow you to inspect the differences.
  107. #
  108. # To allow you to locate changed or new patterns easily they are automatically copied to:
  109. # output/<soc>/changed/
  110. #
  111. # The reference files are stored in $ORIGEN_WORK/.ref <br>
  112. # Very often you will want to compare the output of the current generator to a previous version,
  113. # to do this remove the .ref and replace it with a link to the .ref in another workspace.
  114. # rm -fr .ref
  115. # ln -s <path_to_my_ref_workspace>/.ref .ref
  116. # After that generate and save the patterns in the reference workspace and re-run the patterns in the original
  117. # workspace to see if there are any differences.
  118. #
  119. # ==== Options
  120. # For a list of options run:
  121. # origen help g

lib/origen/commands/helpers.rb

40.0% lines covered

10 relevant lines. 4 lines covered and 6 lines missed.
    
  1. 2 module Origen
  2. 2 module CommandHelpers
  3. 2 def self.extend_options(opts, app_opts, options)
  4. 16 app_opts.each do |app_option|
  5. if app_option.last.is_a?(Proc)
  6. ao_proc = app_option.pop
  7. if ao_proc.arity == 1
  8. opts.on(*app_option) { ao_proc.call(options) }
  9. else
  10. opts.on(*app_option) { |arg| ao_proc.call(options, arg) }
  11. end
  12. else
  13. opts.on(*app_option) {}
  14. end
  15. end
  16. end
  17. end
  18. end

lib/origen/commands/program.rb

89.36% lines covered

47 relevant lines. 42 lines covered and 5 lines missed.
    
  1. 2 require 'optparse'
  2. 2 require 'origen/commands/helpers'
  3. 2 options = {}
  4. # App options are options that the application can supply to extend this command
  5. 2 app_options = @application_options || []
  6. 2 opt_parser = OptionParser.new do |opts|
  7. 2 opts.banner = 'Usage: origen p [space separated files or directories] [options]'
  8. 2 opts.on('-e', '--environment NAME', String, 'Override the default environment, NAME can be a full path or a fragment of an environment file name') { |e| options[:environment] = e }
  9. 3 opts.on('-t', '--target NAME', String, 'Override the default target, NAME can be a full path or a fragment of a target file name') { |t| options[:target] = t }
  10. 2 opts.on('-l', '--lsf [ACTION]', [:clear, :add], "Submit jobs to the LSF, optionally specify whether to 'clear' or 'add' to existing jobs") { |a| options[:lsf] = true; options[:lsf_action] = a }
  11. 2 opts.on('-w', '--wait', 'Wait for LSF processing to complete') { options[:wait_for_lsf_completion] = true }
  12. 2 opts.on('-c', '--continue', 'Continue on error (to the next file)') { options[:continue] = true }
  13. 2 opts.on('-d', '--debugger', 'Enable the debugger') { options[:debugger] = true }
  14. 2 opts.on('-f', '--file FILE', String, 'Override the default log file') { |o| options[:log_file] = o }
  15. 3 opts.on('-pl', '--plugin PLUGIN_NAME', String, 'Set current plugin') { |pl_n| options[:current_plugin] = pl_n }
  16. 2 opts.on('-o', '--output DIR', String, 'Override the default output directory') { |o| options[:output] = o }
  17. 3 opts.on('-r', '--reference DIR', String, 'Override the default reference directory') { |o| options[:reference] = o }
  18. 2 opts.on('--list FILE', String, 'Override the default pattern list file name') { |o| options[:referenced_pattern_list] = o }
  19. 2 opts.on('--doc', 'Generate into doc (yaml) format, requires a Doc interface to be setup in your application') { options[:doc] = true }
  20. 2 opts.on('-q', '--queue NAME', String, 'Specify the LSF queue, default is short') { |o| options[:queue] = o }
  21. 2 opts.on('-p', '--project NAME', String, 'Specify the LSF project, default is msg.te') { |o| options[:project] = o }
  22. 2 opts.on('-d', '--debugger', 'Enable the debugger') { options[:debugger] = true }
  23. 2 opts.on('-m', '--mode MODE', Origen::Mode::MODES, 'Force the Origen operating mode:', ' ' + Origen::Mode::MODES.join(', ')) { |_m| }
  24. # Apply any application option extensions to the OptionParser
  25. 2 Origen::CommandHelpers.extend_options(opts, app_options, options)
  26. 2 opts.separator ''
  27. 2 opts.on('-h', '--help', 'Show this message') { puts opts; exit }
  28. end
  29. 2 opt_parser.parse! ARGV
  30. 2 options[:files] = ARGV
  31. 2 Origen.load_application
  32. 2 if options[:queue]
  33. Origen.config.lsf.queue = options.delete(:queue)
  34. end
  35. 2 if options[:project]
  36. Origen.config.lsf.project = options.delete(:project)
  37. end
  38. 2 def self._with_doc_tester(options)
  39. 2 if options[:doc]
  40. Origen.app.with_doc_tester do
  41. yield
  42. end
  43. else
  44. 2 yield
  45. end
  46. end
  47. 2 _with_doc_tester(options) do
  48. 2 Origen.app.plugins.temporary = options[:current_plugin] if options[:current_plugin]
  49. 2 Origen.environment.temporary = options[:environment] if options[:environment]
  50. 2 Origen.target.temporary = options[:target] if options[:target]
  51. 2 Origen.app.load_target! # This initial load is required to apply any configuration
  52. # options present in the target, it will loaded again before
  53. # each generate/compile job
  54. 2 if Origen.config.test_program_output_directory && !options[:output]
  55. options[:output] = Origen.config.test_program_output_directory
  56. end
  57. 2 options[:action] = :program # Let the generator know this is a test program generation
  58. 2 Origen.app.runner.launch(options)
  59. end
  60. 2 Origen.lsf.wait_for_completion if options[:wait_for_lsf_completion]

lib/origen/controller.rb

84.0% lines covered

75 relevant lines. 63 lines covered and 12 lines missed.
    
  1. 2 module Origen
  2. 2 module Controller
  3. 2 extend ActiveSupport::Concern
  4. 2 module ClassMethods
  5. 2 def model(options = {})
  6. 3 options[:controller_class] = self
  7. 3 if options[:path]
  8. 1 @path_to_model = options[:path]
  9. else
  10. 2 options[:model_class] = _resolve_model_class(options)
  11. end
  12. 3 Origen.controllers << options
  13. end
  14. 2 def path_to_model
  15. 2 @path_to_model
  16. end
  17. 2 def _resolve_model_class(options)
  18. 2 class_name = options[:class_name]
  19. 2 if class_name
  20. 2 if eval("defined? #{_namespace}::#{class_name}")
  21. 2 klass = eval("#{_namespace}::#{class_name}")
  22. else
  23. if eval("defined? #{class_name}")
  24. klass = eval(class_name)
  25. else
  26. if eval("defined? #{self}::#{class_name}")
  27. klass = eval("#{self}::#{class_name}")
  28. else
  29. puts "Could not find class: #{class_name}"
  30. fail 'Unknown model class!'
  31. end
  32. end
  33. end
  34. 2 klass
  35. else
  36. fail "You must supply a :class_name option when defining a controller's model!"
  37. end
  38. end
  39. 2 def _namespace
  40. 4 to_s.sub(/::[^:]*$/, '')
  41. end
  42. end
  43. 2 def inspect
  44. 2 if model
  45. 2 "<Model/Controller: #{model.class}:#{model.object_id}/#{self.class}:#{object_id}>"
  46. else
  47. "<Controller: #{self.class}:#{object_id}>"
  48. end
  49. end
  50. 2 def is_a?(*args)
  51. 171 if model
  52. 171 super(*args) || model.is_a?(*args)
  53. else
  54. super(*args)
  55. end
  56. end
  57. # Returns the controller's model
  58. 2 def model
  59. 1066 @model ||= begin
  60. 1 if self.class.path_to_model
  61. 1 m = eval(self.class.path_to_model)
  62. 1 if m
  63. 1 if m.respond_to?(:_controller=)
  64. m.send(:_controller=, self)
  65. end
  66. else
  67. fail "No model object found at path: #{self.class.path_to_model}"
  68. end
  69. 1 m
  70. end
  71. end
  72. end
  73. # When compared to another object, a controller will consider itself equal if either the controller
  74. # or its model match the given object
  75. 2 def ==(obj, options = {})
  76. 13 if obj.is_a?(Origen::SubBlocks::Placeholder)
  77. 1 obj = obj.materialize
  78. end
  79. 13 if options[:called_from_model]
  80. 11 super(obj)
  81. else
  82. 2 super(obj) || model == obj
  83. end
  84. end
  85. 2 alias_method :equal?, :==
  86. # Means that when dealing with a controller/model pair, you can
  87. # always call obj.model and obj.controller to get the one you want,
  88. # regardless of the one you currently have.
  89. 2 def controller
  90. 4 self
  91. end
  92. 2 def respond_to?(*args)
  93. 1691 super || !!(!@respond_directly && model && model.respond_to_directly?(*args))
  94. end
  95. 2 def respond_to_directly?(*args)
  96. 1482 @respond_directly = true
  97. 1482 result = respond_to?(*args)
  98. 1482 @respond_directly = false
  99. 1482 result
  100. end
  101. 2 def to_json(*args)
  102. 1 model.to_json(*args)
  103. end
  104. # Used to proxy all method and attribute requests not implemented on the controller
  105. # to the model.
  106. #
  107. # On first call of a missing method a method is generated to avoid the missing lookup
  108. # next time, this should be faster for repeated lookups of the same method, e.g. reg
  109. 2 def method_missing(method, *args, &block)
  110. 151 if model.respond_to?(method)
  111. # This method is handled separately since it is important to produce a proxy method
  112. # that takes no arguments, otherwise the register address lookup system mistakes it
  113. # for a legacy way of calculating the base address whereby the register itself was
  114. # given as an argument.
  115. 151 if method.to_sym == :base_address
  116. 9 define_singleton_method(method) do
  117. 9 model.send(method)
  118. end
  119. 9 base_address
  120. else
  121. 142 define_singleton_method(method) do |*args, &block|
  122. 180 model.send(method, *args, &block)
  123. end
  124. 142 send(method, *args, &block)
  125. end
  126. else
  127. super
  128. end
  129. end
  130. 2 private
  131. 2 def _model=(model)
  132. 285 @model = model
  133. end
  134. end
  135. end

lib/origen/database.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Database
  3. 2 autoload :KeyValueStore, 'origen/database/key_value_store'
  4. 2 autoload :KeyValueStores, 'origen/database/key_value_stores'
  5. end
  6. end

lib/origen/database/key_value_store.rb

74.68% lines covered

79 relevant lines. 59 lines covered and 20 lines missed.
    
  1. 2 module Origen
  2. 2 module Database
  3. 2 class KeyValueStore
  4. 2 attr_reader :name
  5. # Returns the parent database (the application's collection of
  6. # key-value stores)
  7. 2 attr_reader :database
  8. 2 attr_accessor :private
  9. 2 def initialize(database, name)
  10. 8 @name = name
  11. 8 @database = database
  12. 8 @private = false
  13. end
  14. # Read a value from the store
  15. 2 def [](key)
  16. 1775 refresh if stale?
  17. 1775 store[key]
  18. end
  19. # Persist a new value to the store
  20. 2 def []=(key, val)
  21. 24 refresh if persisted?
  22. 24 store[key] = val
  23. 24 save_to_file
  24. 24 val
  25. end
  26. # Force a refresh of the database
  27. 2 def refresh
  28. unless @uncommitted || !persisted?
  29. dssc.check_out(file, version: 'Trunk', force: true)
  30. record_refresh
  31. end
  32. end
  33. 2 def record_refresh
  34. database.record_refresh(name)
  35. @store = nil
  36. end
  37. # Returns true if the database is due a time-based refresh, note that
  38. # this has no bearing on whether or not someone else has committed to
  39. # the store since the last refresh
  40. 2 def stale?
  41. 1775 if persisted?
  42. t = database.time_since_refresh(name)
  43. !t || store[:refresh_interval_in_minutes] == 0 || t > store[:refresh_interval_in_minutes]
  44. else
  45. 1775 false
  46. end
  47. end
  48. 2 def persisted?
  49. 1916 database.persisted?
  50. end
  51. 2 def private?
  52. 25 @private
  53. end
  54. # Check if the store has a key
  55. 2 def has_key?(key)
  56. 2 store.include? key
  57. end
  58. # Remove the session file in the case it gets corrupted
  59. # This can happen when a complex object is not handled
  60. # correctly by the Marshal method.
  61. 2 def rm_session_file
  62. FileUtils.rm_f(file)
  63. end
  64. # Deletes a key from the active store
  65. 2 def delete_key(key)
  66. 1 store.delete(key)
  67. 1 save_to_file
  68. 1 load_from_file
  69. end
  70. # Return an array of store keys, excluding the 'infrastructure' key(s)
  71. 2 def keys
  72. 1 store.keys - [:refresh_interval_in_minutes]
  73. end
  74. 2 private
  75. 2 def dssc
  76. @dssc ||= Origen::Utility::DesignSync.new
  77. end
  78. 2 def store
  79. 1828 @store ||= begin
  80. 8 if file.exist?
  81. 8 load_from_file
  82. elsif persisted? && dssc.managed_by_design_sync?(file)
  83. refresh
  84. load_from_file
  85. else
  86. @uncommitted = true
  87. { refresh_interval_in_minutes: 60 }
  88. end
  89. end
  90. end
  91. 2 def load_from_file
  92. 9 s = nil
  93. 9 File.open(file.to_s) do |f|
  94. 9 s = Marshal.load(f)
  95. end
  96. 9 s
  97. end
  98. 2 def save_to_file
  99. 25 unless file.dirname.exist?
  100. FileUtils.mkdir_p(file.dirname.to_s)
  101. end
  102. 25 if @uncommitted
  103. database.record_new_store(name)
  104. @uncommitted = false
  105. end
  106. 25 File.open(file.to_s, 'w') do |f|
  107. 25 Marshal.dump(store, f)
  108. end
  109. 25 if private?
  110. FileUtils.chmod(0600, file)
  111. else
  112. 25 FileUtils.chmod(0664, file)
  113. end
  114. 25 if persisted?
  115. dssc.check_in file, new: true, keep: true, branch: 'Trunk'
  116. end
  117. end
  118. 2 def file
  119. 92 file_path = database.app == Origen ? Origen.home : database.app.root
  120. 92 if persisted?
  121. @file ||= Pathname.new("#{file_path}/.db/#{name.to_s.symbolize}")
  122. else
  123. 92 @file ||= Pathname.new("#{file_path}/.session/#{name.to_s.symbolize}")
  124. end
  125. end
  126. end
  127. end
  128. end

lib/origen/database/key_value_stores.rb

56.36% lines covered

55 relevant lines. 31 lines covered and 24 lines missed.
    
  1. 2 module Origen
  2. 2 module Database
  3. 2 class KeyValueStores
  4. # Returns the application that owns the database
  5. 2 attr_reader :app
  6. 2 def initialize(app, options = {})
  7. options = {
  8. 6 persist: true
  9. }.merge(options)
  10. 6 @app = app
  11. 6 @persist = options[:persist]
  12. end
  13. 2 def inspect
  14. if persisted?
  15. app == Origen ? "Origen's Global Database" : "< #{app.class}'s Database >"
  16. else
  17. app == Origen ? "Origen's Global Session" : "< #{app.class}'s Session >"
  18. end
  19. end
  20. # Refresh all stores
  21. 2 def refresh
  22. if persisted?
  23. _system.refresh
  24. files = stores.map { |name| send(name).send(:file) }
  25. dssc.check_out(files.join(' '), version: 'Trunk', force: true)
  26. stores.each { |name| send(name).record_refresh }
  27. end
  28. nil
  29. end
  30. # Returns the time in minutes since the given store
  31. # was last refreshed
  32. 2 def time_since_refresh(name)
  33. if persisted?
  34. if refresh_table[name]
  35. ((Time.now - refresh_table[name]) / 60).floor
  36. end
  37. else
  38. Time.now
  39. end
  40. end
  41. # Record that the given store was just refreshed
  42. 2 def record_refresh(name)
  43. if persisted?
  44. t = refresh_table
  45. t[name] = Time.now
  46. app.session._database[:refresh_table] = t
  47. end
  48. end
  49. 2 def record_new_store(name)
  50. unless name == :_system || name == :_database
  51. _system.refresh
  52. s = stores
  53. s << name unless s.include?(name)
  54. _system[:stores] = s
  55. end
  56. end
  57. # Used to create new key value stores on the fly.
  58. #
  59. # On first call of a missing method a method is generated to avoid the missing lookup
  60. # next time, this should be faster for repeated lookups of the same method, e.g. reg
  61. 2 def method_missing(method, *args, &block)
  62. 7 if method.to_s =~ /(=|\(|\)|\.|\[|\]|{|}|\\|\/)/ || [:test, :_system].include?(method)
  63. fail "Invalid database name: #{method}"
  64. else
  65. 7 define_singleton_method(method) do
  66. 1802 loaded[method] ||= KeyValueStore.new(self, method)
  67. end
  68. end
  69. 7 send(method, *args, &block)
  70. end
  71. # Returns the names of all known stores
  72. 2 def stores
  73. 1 _system[:stores] || []
  74. end
  75. 2 def persisted?
  76. 1916 @persist
  77. end
  78. 2 def has_key?(key)
  79. 1 stores.include? key
  80. end
  81. 2 private
  82. 2 def refresh_table
  83. app.session._database[:refresh_table] ||= {}
  84. end
  85. # Persisted key value store used by the database system
  86. 2 def _system
  87. 1 @_system ||= KeyValueStore.new(self, :_system)
  88. end
  89. 2 def dssc
  90. @dssc ||= Origen::Utility::DesignSync.new
  91. end
  92. 2 def loaded
  93. 1802 @loaded ||= {}
  94. end
  95. end
  96. end
  97. end

lib/origen/errata/hw_erratum.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Errata
  3. 2 class HwErratum
  4. # ID number used to identify erratum
  5. 2 attr_reader :id
  6. # Erratum Title
  7. 2 attr_accessor :title
  8. # Description of erratum
  9. 2 attr_accessor :description
  10. # Description of the hardware workaround for the erratum
  11. 2 attr_accessor :hw_workaround_description
  12. # How the errata is to be distributed ex:
  13. # --Internal Only
  14. # --Customer visible
  15. # --Other: 3rd party, etc.
  16. 2 attr_accessor :disposition
  17. # Impact of erratum to customer
  18. 2 attr_accessor :impact
  19. # When/if the erratum will be fixed
  20. 2 attr_accessor :fix_plan
  21. # IP block that is associate with this errata
  22. 2 attr_accessor :ip_block
  23. # Software workaround object associated with erratum
  24. 2 attr_accessor :sw_workaround
  25. 2 def initialize(id, ip_block, overview = {}, status = {}, sw_workaround = {})
  26. 3 @id = id
  27. 3 @ip_block = ip_block
  28. 3 @title = overview[:title]
  29. 3 @description = overview[:description]
  30. 3 @hw_workaround_description = overview[:hw_workaround_description]
  31. 3 @disposition = status[:disposition]
  32. 3 @impact = status[:impact]
  33. 3 @fix_plan = status[:fix_plan]
  34. 3 @sw_workaround = sw_workaround
  35. end
  36. end
  37. end
  38. end

lib/origen/errata/sw_erratum_workaround.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Errata
  3. 2 class SwErratumWorkaround
  4. # ID number used to identify software workaround
  5. 2 attr_reader :id
  6. # Title of software workaround
  7. 2 attr_accessor :title
  8. # Description of software workaround and implementation
  9. 2 attr_accessor :description
  10. # Availability of workaround, ex:
  11. # -- Not Applicable: Errata does not affect software
  12. # -- Not Available: Workaround not available
  13. # -- Available: Workaround is available to be distributed
  14. 2 attr_accessor :sw_disposition
  15. # Software distribution version which incorporates the workaround
  16. 2 attr_accessor :distribution
  17. # Release note
  18. 2 attr_accessor :note
  19. # Link to patch(s) for workaround
  20. 2 attr_accessor :patches
  21. 2 def initialize(id, overview = {}, resolution = {})
  22. 5 @id = id
  23. 5 @title = overview[:title]
  24. 5 @description = overview[:description]
  25. 5 @sw_disposition = overview[:sw_disposition]
  26. 5 @distribution = overview[:distribution]
  27. 5 @note = resolution[:note]
  28. 5 @patches = resolution[:patches]
  29. end
  30. end
  31. end
  32. end

lib/origen/features/feature.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Features
  3. 2 class Feature
  4. 2 attr_reader :name
  5. 2 attr_reader :description
  6. 2 def initialize(name, options = {})
  7. 8 @name = name
  8. 8 @description = options[:description]
  9. end
  10. 2 def describe
  11. 3 return 'No description provided!' if @description == []
  12. 2 if @description.class == Array
  13. 1 @description.join(' ')
  14. else
  15. 1 @description
  16. end
  17. end
  18. end
  19. end
  20. end

lib/origen/file_handler.rb

67.08% lines covered

243 relevant lines. 163 lines covered and 80 lines missed.
    
  1. 2 require 'pathname'
  2. 2 module Origen
  3. # All logic for working with files/directories and resolving path names
  4. # should be included here.
  5. #
  6. # An instance of this class is available as Origen.file_handler
  7. #
  8. # Some portions of Origen may implement local code to do this, but these
  9. # should all be transitioned to use this over time.
  10. 2 class FileHandler
  11. 2 attr_accessor :default_extension
  12. # Returns an array of file/pattern names lines from a list file.
  13. # This will also take care of recursively expanding any embedded
  14. # list references.
  15. 2 def expand_list(files, options = {})
  16. options = {
  17. 22 preserve_duplicates: tester && tester.try(:sim?)
  18. }.merge(options)
  19. 22 list_of_files = [files].flatten.map do |file|
  20. 50 f = file.strip
  21. # Takes care of blank or comment lines in a list file
  22. 50 if f.empty? || f =~ /^\s*#/
  23. 8 nil
  24. # Don't expand program lists when submitting to lsf,
  25. # there are likely to be relational dependencies between
  26. # flows meaning that they must be generated together
  27. 42 elsif is_a_list?(f) && !(options[:lsf] && options[:action] == :program)
  28. 4 expand_list(open_list(f), options)
  29. else
  30. 38 f
  31. end
  32. end.flatten.compact
  33. 22 if options[:preserve_duplicates]
  34. 1 list_of_files
  35. else
  36. 21 list_of_files.uniq
  37. end
  38. end
  39. # Returns the contents of the given list file in an array, if it
  40. # can be found, if not will raise an error
  41. 2 def open_list(file)
  42. 4 f = clean_path_to(file, allow_missing: true)
  43. 4 if f
  44. 3 f = File.open(f, 'r')
  45. 1 elsif File.exist?("#{Origen.root}/list/#{File.basename(file)}")
  46. 1 f = File.open("#{Origen.root}/list/#{File.basename(file)}", 'r')
  47. elsif @last_opened_list_dir && File.exist?("#{@last_opened_list_dir}/#{file}")
  48. f = File.open("#{@last_opened_list_dir}/#{file}", 'r')
  49. else
  50. fail "Could not find list file: #{file}"
  51. end
  52. 4 lines = f.readlines
  53. 4 f.close
  54. # Before we go save the directory of this list, this will help
  55. # us to resolve any relative path references to other lists that
  56. # it may contain
  57. 4 @last_opened_list_dir = clean_path_to(Pathname.new(f).dirname)
  58. 4 lines
  59. end
  60. # Returns true if the input argument is a list, for now this is
  61. # simply defined by the filename ending in .list
  62. 2 def is_a_list?(file)
  63. 42 !!(file =~ /list$/)
  64. end
  65. # Yields absolute paths to the given file or directory. If a directory is supplied
  66. # the method will recurse into the sub directories and ultimately yield
  67. # every file contained within the directory and its children.
  68. 2 def resolve_files(file_or_dir_path, options = {}, &block)
  69. options = {
  70. # Set to :template when calling to consider references to template
  71. # files from an import library
  72. 30 import: false
  73. }.merge(options)
  74. 30 [file_or_dir_path].flatten.each do |file_or_dir_path|
  75. 30 path = inject_import_path(file_or_dir_path, type: options[:import]) if options[:import]
  76. 30 path = clean_path_to(file_or_dir_path, options)
  77. 30 self.base_directory = path unless options[:internal_call]
  78. 30 if path.directory?
  79. 5 Dir.glob("#{path}/*").sort.each do |file|
  80. 20 resolve_files(file, { internal_call: true }.merge(options), &block)
  81. end
  82. else
  83. # Ignore files with the given prefix if supplied, but only if this is a file that
  84. # has been found, if explicitly asked to compile a file from the caller do it regardless
  85. 25 if options[:ignore_with_prefix] && options[:internal_call]
  86. 18 return nil if path.basename.to_s =~ /^#{options[:ignore_with_prefix]}/
  87. end
  88. 13 yield path
  89. end
  90. end
  91. end
  92. # Returns a full path to the given file or directory, raises an error if it
  93. # can't be resolved
  94. 2 def clean_path_to(file, options = {})
  95. # Allow individual calls to this method to specify additional custom load paths to consider
  96. 76 if options[:load_paths]
  97. 3 Array(options[:load_paths]).each do |root|
  98. 6 if File.exist?("#{root}/#{file}")
  99. 2 return Pathname.new("#{root}/#{file}")
  100. end
  101. end
  102. end
  103. 74 if File.exist?(file)
  104. 55 if Pathname.new(file).absolute?
  105. 44 Pathname.new(file)
  106. else
  107. 11 Pathname.new("#{Pathname.pwd}/#{file}")
  108. end
  109. # Is it a relative reference within a list file?
  110. 19 elsif @last_opened_list_dir && File.exist?("#{@last_opened_list_dir}/#{file}")
  111. 3 Pathname.new("#{@last_opened_list_dir}/#{file}")
  112. # Is it a relative reference to the current base directory?
  113. 16 elsif File.exist?("#{base_directory}/#{file}")
  114. 10 Pathname.new("#{base_directory}/#{file}")
  115. # Is it a path relative to Origen.root?
  116. 6 elsif File.exist?("#{Origen.root}/#{file}")
  117. Pathname.new("#{Origen.root}/#{file}")
  118. # Is it a path relative to the current directory?
  119. 6 elsif current_directory && File.exist?("#{current_directory}/#{file}")
  120. 4 Pathname.new("#{current_directory}/#{file}")
  121. # Is it a path relative to the current plugin's Origen.root?
  122. 2 elsif Origen.app.plugins.current && File.exist?("#{Origen.app.plugins.current.root}/#{file}")
  123. Pathname.new("#{Origen.app.plugins.current.root}/#{file}")
  124. 2 elsif options[:default_dir]
  125. m = all_matches(file, options)
  126. if m
  127. Pathname.new(m)
  128. else
  129. if options[:allow_missing]
  130. return nil
  131. else
  132. fail "Can't find: #{file}"
  133. end
  134. end
  135. else
  136. 2 if options[:allow_missing]
  137. return nil
  138. else
  139. fail "Can't find: #{file}"
  140. end
  141. end
  142. end
  143. 2 def check(path)
  144. file_plugin = Origen.app.plugins.path_within_a_plugin(path)
  145. if file_plugin
  146. if Origen.app.plugins.current
  147. if file_plugin == Origen.app.plugins.current.name
  148. return path
  149. else
  150. puts "The requested file is from plugin #{file_plugin} and current system plugin is set to plugin #{Origen.app.plugins.current.name}!"
  151. fail 'Incorrect plugin error!'
  152. end
  153. else
  154. Origen.app.plugins.temporary = file_plugin
  155. return path
  156. end
  157. else
  158. return path
  159. end
  160. end
  161. 2 def all_matches(file, options)
  162. if Origen.app.plugins.current
  163. matches = Dir.glob("#{options[:default_dir]}/#{Origen.app.plugins.current.name}/**/#{file}").sort
  164. matches = matches.flatten.uniq
  165. if matches.size == 0
  166. matches = Dir.glob("#{options[:default_dir]}/**/#{file}").sort
  167. matches = matches.flatten.uniq
  168. end
  169. else
  170. matches = (Dir.glob("#{options[:default_dir]}/**/#{file}") + # Avoids symlinks
  171. Dir.glob("#{options[:default_dir]}/#{file}")).sort
  172. if matches.size == 0
  173. matches = Dir.glob("#{options[:default_dir]}/**{,/*/**}/#{file}").sort # Takes symlinks into consideration
  174. end
  175. matches = matches.flatten.uniq
  176. end
  177. if matches.size == 0
  178. return nil
  179. elsif matches.size > 1
  180. puts 'The following matches were found:'
  181. puts matches
  182. fail "Ambiguous file #{file}"
  183. else
  184. return check(matches.first)
  185. end
  186. end
  187. # Returns an absolute path for the given
  188. 2 def relative_to_absolute(path)
  189. 75 if Pathname.new(path).absolute?
  190. 59 Pathname.new(path)
  191. else
  192. 16 Pathname.new("#{Pathname.pwd}/#{path}")
  193. end
  194. end
  195. 2 def relative_path_to(path)
  196. clean_path_to(path).relative_path_from(Pathname.pwd)
  197. end
  198. 2 def clean_path_to_sub_template(file)
  199. 24 if File.exist?(file)
  200. 6 if Pathname.new(file).absolute?
  201. 6 return Pathname.new(file)
  202. else
  203. return Pathname.new("#{Pathname.pwd}/#{file}")
  204. end
  205. end
  206. 18 file = inject_import_path(file, type: :template)
  207. 18 file = add_underscore_to(file)
  208. 18 file = add_extension_to(file)
  209. 18 web_file = file =~ /\.(html|md)(\.|$)/
  210. begin
  211. # Allow relative references to templates/web when compiling a web template
  212. 18 if Origen.lsf.current_command == 'web' || web_file
  213. 3 clean_path_to(file, load_paths: ["#{Origen.root}/app/templates/web", "#{Origen.root}/templates/web"])
  214. else
  215. 15 clean_path_to(file)
  216. end
  217. rescue
  218. # Try again without .erb
  219. file = file.gsub('.erb', '')
  220. if Origen.lsf.current_command == 'web' || web_file
  221. clean_path_to(file, load_paths: ["#{Origen.root}/app/templates/web", "#{Origen.root}/templates/web"])
  222. else
  223. clean_path_to(file)
  224. end
  225. end
  226. end
  227. 2 def clean_path_to_template(file)
  228. 1 file = inject_import_path(file, type: :template)
  229. 1 file = add_extension_to(file)
  230. 1 clean_path_to(file)
  231. end
  232. # If the current path looks like it is a reference to an import, the
  233. # path will be replaced with the absolute path to the local import directory
  234. 2 def inject_import_path(path, options = {})
  235. 37 path = path.to_s unless path.is_a?(String)
  236. 37 if path =~ /(.*?)\/.*/
  237. 24 import_name = Regexp.last_match[1].downcase.to_sym
  238. 24 if import_name == :origen || import_name == :origen_core || Origen.app.plugins.names.include?(import_name) ||
  239. import_name == :doc_helpers
  240. # Special case to allow a shortcut for this common import plugin and to also handle legacy
  241. # code from when it was called doc_helpers instead of origen_doc_helpers
  242. if import_name == :doc_helpers
  243. root = Origen.app(:origen_doc_helpers).root
  244. else
  245. unless import_name == :origen || import_name == :origen_core
  246. root = Origen.app(import_name).root
  247. end
  248. end
  249. if options[:type] == :template
  250. if import_name == :origen || import_name == :origen_core
  251. path.sub! 'origen', "#{Origen.top}/templates/shared"
  252. else
  253. if File.exist?("#{root}/app/templates/shared")
  254. path.sub! Regexp.last_match[1], "#{root}/app/templates/shared"
  255. else
  256. path.sub! Regexp.last_match[1], "#{root}/templates/shared"
  257. end
  258. end
  259. else
  260. fail 'Unknown import path type!'
  261. end
  262. end
  263. end
  264. 37 path
  265. end
  266. # @deprecated
  267. 2 def clean_path_to_sub_program(file)
  268. Origen.deprecated 'Origen.file_handler.clean_path_to_sub_program is deprecated, update to use version ... of origen_testers instead.'
  269. file = add_underscore_to(file)
  270. file = add_rb_to(file)
  271. clean_path_to(file)
  272. end
  273. # Insert _ in file name if not present
  274. 2 def add_underscore_to(file)
  275. 30 f = Pathname.new(file)
  276. 30 if f.basename.to_s =~ /^_/
  277. file
  278. else
  279. 30 "#{f.dirname}/_#{f.basename}"
  280. end
  281. end
  282. 2 def add_rb_to(file)
  283. 12 f = Pathname.new(file)
  284. 12 "#{f.dirname}/#{f.basename('.rb')}.rb"
  285. end
  286. 2 def add_extension_to(file)
  287. 19 f = Pathname.new(file)
  288. 19 if f.basename('.erb').extname.empty?
  289. 14 if default_extension
  290. 2 "#{f.dirname}/#{f.basename('.erb')}#{default_extension}.erb"
  291. else
  292. 12 "#{f.dirname}/#{f.basename('.erb')}#{Pathname.new(Origen.file_handler.current_file).basename('.erb').extname}.erb"
  293. end
  294. else
  295. 5 "#{f.dirname}/#{f.basename('.erb')}.erb"
  296. end
  297. end
  298. 2 def set_output_directory(options = {})
  299. options = {
  300. 193 create: true
  301. }.merge(options)
  302. 193 if options[:output]
  303. 32 @output_directory = relative_to_absolute(options[:output])
  304. else
  305. 161 @output_directory = Pathname.new(Origen.config.output_directory)
  306. end
  307. 193 if options[:create]
  308. 193 FileUtils.mkdir_p(@output_directory) unless @output_directory.exist?
  309. end
  310. 193 @output_directory
  311. end
  312. # Returns an absolute pathname to the current output directory
  313. 2 def output_directory
  314. 137 @output_directory ||= set_output_directory
  315. end
  316. 2 def set_reference_directory(options = {})
  317. options = {
  318. 193 create: true
  319. }.merge(options)
  320. 193 if options[:reference]
  321. 43 @reference_directory = relative_to_absolute(options[:reference])
  322. else
  323. 150 @reference_directory = Pathname.new(Origen.config.reference_directory)
  324. # Create the reference output directory if it does not exist.
  325. 150 FileUtils.mkdir_p(@reference_directory) unless @reference_directory.exist?
  326. end
  327. 193 if options[:create]
  328. # Delete any broken symlinks in the top level .ref
  329. 193 dir = "#{Origen.root}/.ref"
  330. 193 if File.symlink?(dir)
  331. FileUtils.rm_f(dir) unless File.exist?(dir)
  332. end
  333. 193 FileUtils.mkdir_p(@reference_directory) unless @reference_directory.exist?
  334. end
  335. 193 @reference_directory
  336. end
  337. # Returns an absolute pathname to the current reference directory
  338. 2 def reference_directory
  339. 76 @reference_directory ||= set_reference_directory
  340. end
  341. # Returns the base directory containing the source files being generated/compiled.
  342. #
  343. # When operating on a single file this will return the directory containing that
  344. # file, when operating on a directory this will return the directory.
  345. 2 def base_directory
  346. 99 @base_directory
  347. end
  348. 2 def base_directory=(file_or_dir)
  349. # puts "Base directory changed by: #{caller[0]}"
  350. 36 if file_or_dir.directory?
  351. 29 @base_directory = file_or_dir
  352. else
  353. 7 @base_directory = file_or_dir.dirname
  354. end
  355. end
  356. 2 def current_directory
  357. 16 return @current_directory if @current_directory
  358. 6 @current_directory = clean_path_to(current_file).dirname if current_file
  359. end
  360. 2 def current_file=(file)
  361. 147 @current_directory = nil
  362. 147 @current_file = file
  363. end
  364. 2 def current_file
  365. 526 @current_file
  366. end
  367. 2 def preserve_current_file
  368. 2 file = current_file
  369. 2 yield
  370. 2 self.current_file = file
  371. end
  372. 2 def preserve_state
  373. 28 file = current_file
  374. 28 dir = base_directory
  375. 28 output = output_directory
  376. 28 ref = reference_directory
  377. 28 ext = default_extension
  378. 28 yield
  379. 28 self.base_directory = dir if dir
  380. 28 self.current_file = file if file
  381. 28 set_output_directory(output: output) if output
  382. 28 set_reference_directory(reference: ref) if ref
  383. 28 self.default_extension = ext
  384. end
  385. 2 def preserve_and_clear_state
  386. file = current_file
  387. dir = base_directory
  388. output = output_directory
  389. ref = reference_directory
  390. ext = default_extension
  391. current_file = nil
  392. base_directory = nil
  393. output_directory = nil
  394. reference_directory = nil
  395. yield
  396. self.base_directory = dir if dir
  397. self.current_file = file if file
  398. set_output_directory(output: output) if output
  399. set_reference_directory(reference: ref) if ref
  400. self.default_extension = ext
  401. end
  402. # Returns the sub directory of the current base directory that the
  403. # given file is in
  404. 2 def sub_dir_of(file, base = base_directory)
  405. 36 file = Pathname.new(file) unless file.respond_to?(:relative_path_from)
  406. 36 base = Pathname.new(base) unless base.respond_to?(:relative_path_from)
  407. 36 rel = file.relative_path_from(base)
  408. 36 if file.directory?
  409. rel
  410. else
  411. 36 rel.dirname
  412. end
  413. end
  414. # Convenience method to use when you want to write to a file, this takes
  415. # care of ensuring that the directory exists prior to attempting to open
  416. # the file
  417. 2 def open_for_write(path)
  418. dir = Pathname.new(path).dirname
  419. FileUtils.mkdir_p(dir) unless File.exist?(dir)
  420. File.open(path, 'w') do |f|
  421. yield f
  422. end
  423. end
  424. end
  425. end

lib/origen/generator/compiler.rb

73.86% lines covered

153 relevant lines. 113 lines covered and 40 lines missed.
    
  1. 2 module Origen
  2. 2 class Generator
  3. 2 class Compiler # :nodoc: all
  4. 2 require 'fileutils'
  5. 2 require 'erb'
  6. 2 require 'pathname'
  7. 2 require "#{Origen.top}/helpers/url"
  8. 2 include Helpers
  9. 2 include Comparator
  10. 2 include Renderer
  11. # During a compile this will return the current top-level file being compiled
  12. #
  13. # @example
  14. # Origen.generator.compiler.current_file # => Pathname
  15. 2 attr_reader :current_file
  16. # Where compile will place the compiled content in an output file, this method will return
  17. # it as a string to the caller (i.e. without creating an output file)
  18. #
  19. # It expects an absolute path to a single template file as the file argument.
  20. #
  21. # @api private
  22. 2 def compile_inline(file, options = {})
  23. 11 initial_options = options.merge({})
  24. options = {
  25. 11 check_for_changes: false,
  26. sub_template: false,
  27. collect_stats: false,
  28. initial_options: initial_options
  29. }.merge(options)
  30. 11 @scope = options[:scope]
  31. 11 file = Pathname.new(file) unless options[:string]
  32. 11 run_erb(file, options).strip
  33. end
  34. # Compile all files found under the source directory, non-erb files will be copied
  35. # to the destination un-altered
  36. 2 def compile(file_or_dir, options = {})
  37. options = {
  38. 31 check_for_changes: true,
  39. sub_template: false,
  40. collect_stats: true
  41. }.merge(options)
  42. 31 @scope = options[:scope]
  43. # Doing here so the output_directory (requiring target load) doesn't get hit if
  44. # it is already defined
  45. 31 options[:output_directory] ||= output_directory
  46. 31 @check_for_changes = options[:check_for_changes]
  47. 31 @options = options
  48. 31 if options[:sub_template]
  49. 23 block = options.delete(:block)
  50. 23 if is_erb?(file_or_dir)
  51. 23 run_erb(file_or_dir, options, &block)
  52. else
  53. f = File.open(file_or_dir)
  54. content = f.read
  55. f.close
  56. insert(content)
  57. end
  58. else
  59. 8 Origen.file_handler.resolve_files(file_or_dir, ignore_with_prefix: '_', import: :template) do |file|
  60. 9 compile_file(file, options)
  61. end
  62. end
  63. end
  64. 2 def merge(file_or_dir, options = {})
  65. # Compile an up to date reference
  66. compile(file_or_dir_path, check_for_changes: false, output_directory: merge_reference_directory)
  67. diffs = []
  68. Origen.file_handler.resolve_files(file_or_dir, ignore_with_prefix: '_') do |file|
  69. diffs << merge_file(file, options)
  70. end
  71. diffs.compact!
  72. puts ''
  73. if diffs.size > 0
  74. puts 'The following differences are present in the compiled files and must be resolved manually:'
  75. puts ''
  76. diffs.each do |diff|
  77. puts diff
  78. end
  79. puts ''
  80. else
  81. puts 'Merged successfully!'
  82. end
  83. end
  84. 2 def stats
  85. 10 Origen.app.stats
  86. end
  87. # Compile the supplied file if it is an erb template writing the compiled
  88. # version to the destination directory.
  89. # If the file is not an erb template it is simply copied un-altered to the
  90. # destination directory.
  91. # File must be an absolute path to the file.
  92. 2 def compile_file(file, options = {})
  93. 9 @current_file = Pathname.new(file)
  94. # This is used when templates are compiled through a test program, but can
  95. # be problematic when used to compile files standalone. In practice this may
  96. # not be an issue except when testing Origen and generating and compiling within
  97. # the same thread, but clearing this here doesn't seem to do any harm.
  98. 9 Origen.file_handler.default_extension = nil
  99. 9 Origen.log.info "Compiling... #{relative_path_to(file)}" unless options[:quiet]
  100. 9 Origen.log.info " Created... #{relative_path_to(output_file(file, options))}" unless options[:quiet]
  101. 9 stats.completed_files += 1 if options[:collect_stats]
  102. 9 if is_erb?(file)
  103. 8 output = run_erb(file, options)
  104. 8 f = output_file(file, options).to_s
  105. 8 if output.is_a?(Pathname)
  106. FileUtils.mv output.to_s, f
  107. else
  108. 16 File.open(f, 'w') { |out| out.puts output }
  109. end
  110. else # Just copy it across
  111. 1 out = output_file(file, options)
  112. # Delete the target if it already exists, this prevents permission denied errors when copying
  113. 1 FileUtils.rm_f(out.to_s) if File.exist?(out.to_s)
  114. 1 FileUtils.cp(file.to_s, out.dirname.to_s)
  115. end
  116. 9 if options[:zip]
  117. `gzip -f -9 #{output_file(file, options)}`
  118. else
  119. 9 if @check_for_changes
  120. 9 check_for_changes(output_file(file, options), reference_file(file, options),
  121. 9 comment_char: Origen.app.tester ? Origen.app.tester.program_comment_char : nil,
  122. compile_job: true)
  123. end
  124. end
  125. end
  126. 2 def run_erb(file, opts = {}, &block)
  127. # Refresh the target to start all settings from scratch each time
  128. # This is an easy way to reset all registered values
  129. 42 if opts[:preserve_target]
  130. 1 options[:preserve_target] = opts.delete(:preserve_target)
  131. end
  132. 42 Origen.app.reload_target! unless options[:preserve_target]
  133. # Record the current file, this can be used to resolve any relative path
  134. # references in the file about to be compiled
  135. 42 Origen.file_handler.current_file = file
  136. # Make the file and options available to the template
  137. 42 if opts[:initial_options] || opts[:options]
  138. 12 options.merge!(opts.delete(:initial_options) || opts.delete(:options))
  139. end
  140. 42 options[:file] = file
  141. 42 options[:top_level_file] = current_file
  142. 42 b = _get_binding(opts, &block)
  143. 42 if opts[:string]
  144. 1 content = file
  145. 1 @current_buffer = '@_string_template'
  146. 1 buffer = @current_buffer
  147. else
  148. 41 content = File.read(file.to_s)
  149. 41 buffer = buffer_name_for(file)
  150. end
  151. 42 if block_given?
  152. 5 content = ERB.new(content, 0, '%<>', buffer).result(b)
  153. else
  154. 37 content = ERB.new(content, 0, Origen.config.erb_trim_mode, buffer).result(b)
  155. end
  156. 42 insert(content)
  157. end
  158. # @api private
  159. 2 def _get_binding(opts, &block)
  160. # Important, don't declare any local variable called options here,
  161. # the scope of this method will be the default for any templates and
  162. # we want options to refer to the global options method
  163. 42 b = opts[:binding] || opts[:scope] || binding
  164. # If an object has been supplied as the scope, then do some tricks
  165. # to get a hold of its internal scope
  166. 42 unless b.is_a?(Binding)
  167. 5 b.define_singleton_method :_get_binding do |local_opts, &_block|
  168. # rubocop:disable Lint/UselessAssignment
  169. 5 options = local_opts
  170. # rubocop:enable Lint/UselessAssignment
  171. 5 binding
  172. end
  173. # Here the global options, the ones visible right now, are passed to into the method defined above,
  174. # they will get assigned to the local variable called option and that is what the template will
  175. # be able to see
  176. 5 b = b._get_binding(options, &block)
  177. end
  178. 42 b
  179. end
  180. 2 def current_buffer
  181. 5 (@scope || self).instance_variable_get(@current_buffer || '@_anonymous')
  182. end
  183. 2 def current_buffer=(text)
  184. 28 (@scope || self).instance_variable_set(@current_buffer || '@_anonymous', text)
  185. end
  186. # Returns the ERB buffer name for the given file, something like "@my_file_name"
  187. 2 def buffer_name_for(file)
  188. 41 expected_filename = file.basename.to_s.chomp('.erb')
  189. 41 expected_filename.gsub!('-', '_') if expected_filename.match(/-/)
  190. 41 expected_filename.gsub!('.', '_') if expected_filename.match(/./)
  191. 41 @current_buffer = '@_' + expected_filename
  192. end
  193. 2 def merge_file(file, _options = {})
  194. file = Pathname.new(file)
  195. Origen.log.info "Merging... #{file.basename}"
  196. if is_erb?(file) && File.exist?(output_file(file))
  197. check_for_differences(output_file(file), merge_ref_file(file), file)
  198. elsif File.exist?(output_file(file))
  199. if check_for_differences(output_file(file), merge_ref_file(file), file)
  200. FileUtils.cp(output_file(file), file.dirname.to_s)
  201. end
  202. end
  203. end
  204. 2 def display_path_to(file)
  205. p = relative_path_to(file).to_s
  206. p.gsub!('/', '\\') if Origen.running_on_windows?
  207. p
  208. end
  209. 2 def check_for_differences(a, b, file)
  210. if check_for_changes(a, b, comment_char: ["'", 'logprint'], quiet: true, compile_job: true)
  211. puts "*** CHANGE DETECTED *** To rollback: #{Origen.config.copy_command} #{display_path_to(b)} #{display_path_to(a)}"
  212. "#{Origen.config.diff_command} #{display_path_to(a)} #{display_path_to(b)} & #{ENV['EDITOR']} #{file.cleanpath} &"
  213. end
  214. end
  215. # Returns true if the supplied file name has a .erb extension
  216. 2 def is_erb?(file)
  217. 32 !!(file.to_s =~ /.erb$/) || !Origen.config.compile_only_dot_erb_files
  218. end
  219. 2 def output_directory
  220. 58 Origen.file_handler.output_directory
  221. end
  222. 2 def reference_directory
  223. 9 Origen.file_handler.reference_directory
  224. end
  225. 2 def merge_reference_directory
  226. "#{Origen.root}/.merge_ref"
  227. end
  228. # Returns the output file corresponding to the given source file, the destination
  229. # directory will be created if it doesn't exist.
  230. 2 def output_file(file, options = {})
  231. options = {
  232. 27 output_directory: output_directory
  233. }.merge(options)
  234. # return @output_file if @output_file
  235. 27 sub_dir = options[:output_sub_dir] || Origen.file_handler.sub_dir_of(file).to_s
  236. 27 sub_dir = nil if sub_dir == '.'
  237. 27 filename = options[:output_file_name] || file.basename.to_s.gsub('.erb', '')
  238. # filename.gsub!('target', $target.id) if filename =~ /target/ && $target.id
  239. 27 output = Pathname.new("#{options[:output_directory]}#{sub_dir ? '/' + sub_dir : ''}/#{filename}")
  240. 27 FileUtils.mkdir_p(output.dirname.to_s) unless File.exist?(output.dirname.to_s)
  241. # @output_file = output
  242. 27 output
  243. end
  244. # Returns the reference file corresponding to the given source file, the destination
  245. # directory will be created if it doesn't exist.
  246. 2 def reference_file(file, options = {})
  247. # return @reference_file if @reference_file
  248. 9 sub_dir = Origen.file_handler.sub_dir_of(file).to_s
  249. 9 sub_dir = nil if sub_dir == '.'
  250. 9 filename = options[:output_file_name] || file.basename.to_s.gsub('.erb', '')
  251. # filename.gsub!('target', $target.id) if filename =~ /target/ && $target.id
  252. 9 reference = Pathname.new("#{reference_directory}#{sub_dir ? '/' + sub_dir : ''}/#{filename}")
  253. 9 FileUtils.mkdir_p(reference.dirname.to_s) unless File.exist?(reference.dirname.to_s)
  254. # @reference_file = reference
  255. 9 reference
  256. end
  257. 2 def merge_ref_file(file, options = {})
  258. options = {
  259. directory: merge_reference_directory
  260. }.merge(options)
  261. # return @merge_ref_file if @merge_ref_file
  262. sub_dir = Origen.file_handler.sub_dir_of(file).to_s
  263. sub_dir = nil if sub_dir == '.'
  264. filename = file.basename.to_s.gsub('.erb', '')
  265. # filename.gsub!('target', $target.id) if filename =~ /target/ && $target.id
  266. output = Pathname.new("#{options[:directory]}#{sub_dir ? '/' + sub_dir : ''}/#{filename}")
  267. FileUtils.mkdir_p(output.dirname.to_s) unless File.exist?(output.dirname.to_s)
  268. # @merge_ref_file = output
  269. output
  270. end
  271. end
  272. end
  273. end

lib/origen/generator/job.rb

85.83% lines covered

127 relevant lines. 109 lines covered and 18 lines missed.
    
  1. 2 module Origen
  2. 2 class Generator
  3. # A job is responsible for executing a single pattern source
  4. 2 class Job # :nodoc: all
  5. 2 attr_accessor :output_file_body, :pattern
  6. 2 attr_reader :split_counter, :split_names
  7. 2 attr_reader :options
  8. 2 def initialize(pattern, options)
  9. 110 @testing = options[:testing]
  10. 110 @options = options
  11. 110 @requested_pattern = pattern
  12. 110 @no_comments = options[:no_comments]
  13. 110 @output_opt = options[:output]
  14. end
  15. # Returns true if the job is a test job, will only be true in a test scenario
  16. 2 def test?
  17. 190 @testing
  18. end
  19. 2 def no_comments?
  20. @no_comments
  21. end
  22. 2 def inc_split_counter(name = '')
  23. 4 @split_counter ||= 0
  24. 4 @split_names ||= ['']
  25. 4 @split_counter += 1
  26. 4 @split_names << name
  27. end
  28. 2 def requested_pattern
  29. 95 @requested_pattern
  30. end
  31. 2 alias_method :requested_file, :requested_pattern
  32. # Returns a full path to the output pattern, note that this is not available
  33. # until the job has been run
  34. 2 def output_pattern
  35. 900 "#{output_pattern_directory}/#{output_pattern_filename}"
  36. end
  37. 2 alias_method :output_file, :output_pattern
  38. 2 def reference_pattern
  39. 88 "#{reference_pattern_directory}/#{output_pattern_filename}"
  40. end
  41. 2 alias_method :reference_file, :reference_pattern
  42. 2 def output_pattern_filename
  43. 1418 return '' if @testing
  44. # If the pattern name has been overridden by an interator use that
  45. 1390 return @output_pattern_filename if @output_pattern_filename
  46. 464 if !@pattern && !@output_file_body
  47. fail 'Sorry the output_pattern is not available until the job has been run'
  48. end
  49. 464 body = @output_file_body ? @output_file_body : File.basename(@pattern, '.rb')
  50. 464 output_prefix + body + output_postfix + split_number + output_extension
  51. end
  52. # This can be modified at runtime by the pattern generator in response to
  53. # iterator substitutions
  54. 2 def output_pattern_filename=(val)
  55. 150 @output_pattern_filename = val
  56. end
  57. 2 def reset_output_pattern_filename
  58. 85 @output_pattern_filename = nil
  59. end
  60. 2 def output_pattern_directory
  61. 988 @output_pattern_directory ||= begin
  62. 42 dir = output_override || Origen.app.config.pattern_output_directory
  63. 42 if tester.respond_to?(:subdirectory)
  64. dir = File.join(dir, tester.subdirectory)
  65. end
  66. 42 FileUtils.mkdir_p(dir) unless File.exist?(dir)
  67. 42 dir
  68. end
  69. end
  70. 2 def reference_pattern_directory
  71. 88 @reference_pattern_directory ||= begin
  72. 28 dir = Origen.file_handler.reference_directory
  73. 28 if tester.respond_to?(:subdirectory)
  74. dir = File.join(dir, tester.subdirectory)
  75. end
  76. 28 FileUtils.mkdir_p(dir) unless File.exist?(dir)
  77. 28 dir
  78. end
  79. end
  80. 2 def output_prefix
  81. 464 p = Origen.config.pattern_prefix ? Origen.config.pattern_prefix + '_' : ''
  82. 464 p = "_#{p}" if Origen.tester.doc?
  83. 464 p
  84. end
  85. 2 def output_postfix
  86. 524 Origen.config.pattern_postfix ? '_' + Origen.config.pattern_postfix : ''
  87. end
  88. 2 def output_extension
  89. 619 '.' + Origen.tester.pat_extension
  90. end
  91. 2 def output_override
  92. 42 if @output_opt
  93. 10 if @output_opt =~ /#{Origen.root}/
  94. 2 return @output_opt
  95. else
  96. 8 return "#{Origen.root}/#{@output_opt}"
  97. end
  98. end
  99. nil
  100. end
  101. 2 def split_number
  102. 464 if split_counter
  103. 52 if split_names[split_counter] != ''
  104. 26 "_#{split_names[split_counter]}"
  105. else
  106. 26 "_part#{split_counter}"
  107. end
  108. else
  109. 412 ''
  110. end
  111. end
  112. 2 def strip_dir_and_ext(name)
  113. 40 Pathname.new(name).basename('.*').basename('.*').to_s
  114. end
  115. 2 def run
  116. 40 Origen.app.current_jobs << self
  117. begin
  118. 40 if @options[:compile]
  119. 8 Origen.log.start_job(strip_dir_and_ext(@requested_pattern), :compiler)
  120. 8 Origen.generator.compiler.compile(@requested_pattern, @options)
  121. 32 elsif @options[:job_type] == :merge
  122. Origen.log.start_job(strip_dir_and_ext(@requested_pattern), :merger)
  123. Origen.generator.compiler.merge(@requested_pattern)
  124. 32 elsif @options[:action] == :program
  125. 4 if Origen.running_simulation?
  126. Origen.log.start_job(strip_dir_and_ext(@requested_pattern), :simulator)
  127. else
  128. 4 Origen.log.start_job(strip_dir_and_ext(@requested_pattern), :program_generator)
  129. end
  130. 4 Origen.flow.reset
  131. 4 Origen.resources.reset
  132. 4 OrigenTesters::Generator.execute_source(@pattern)
  133. else
  134. 28 if Origen.running_simulation?
  135. Origen.log.start_job(strip_dir_and_ext(@requested_pattern), :simulator)
  136. else
  137. 28 Origen.log.start_job(strip_dir_and_ext(@requested_pattern), :pattern_generator)
  138. end
  139. 28 Origen.generator.pattern.reset # Resets the pattern controller ready for a new pattern
  140. # Give the app a chance to handle pattern dispatch
  141. 28 skip = false
  142. 28 Origen.app.listeners_for(:before_pattern_lookup).each do |listener|
  143. 28 skip ||= !listener.before_pattern_lookup(@requested_pattern)
  144. end
  145. 28 unless skip
  146. 28 if @options[:sequence]
  147. 1 @pattern = @requested_pattern
  148. 1 Origen.pattern.sequence do |seq|
  149. # This splits the pattern name by "_" then removes all values that are common to all patterns
  150. # and then rejoins what is left.
  151. # The goal is to keep the thread ID concise for the log and rather than using the whole pattern
  152. # name only focussing on what is different.
  153. # e.g. if you combined patterns flash_read_ckbd_ip1_max.rb and flash_read_ckbd_ip2_max.rb into
  154. # a concurrent sequence then the two threads would be called 'ip1' and 'ip2'.
  155. 1 ids = @options[:patterns].map do |pat|
  156. 2 Pathname.new(pat).basename('.*').to_s.split('_')
  157. end
  158. 14 ids = ids.map { |id| id.reject { |i| ids.all? { |id| id.include?(i) } }.join('_') }
  159. 1 @options[:patterns].each_with_index do |pat, i|
  160. 2 id = ids[i]
  161. 2 id = i.to_s if id.empty?
  162. 2 seq.in_parallel id do
  163. 2 seq.run pat
  164. end
  165. end
  166. end
  167. else
  168. 27 @pattern = Origen.generator.pattern_finder.find(@requested_pattern, @options)
  169. 27 if @pattern.is_a?(Hash)
  170. 1 @output_file_body = @pattern[:output]
  171. 1 @pattern = @pattern[:pattern]
  172. end
  173. 27 load @pattern unless @pattern == :skip # Run the pattern
  174. end
  175. end
  176. end
  177. rescue Exception => e
  178. # Whoever has aborted the job is responsible for cleaning it up
  179. unless e.is_a?(Origen::Generator::AbortError)
  180. if @options[:continue] || Origen.running_remotely?
  181. Origen.log.error "FAILED - #{@requested_pattern} (for target #{Origen.target.name})"
  182. Origen.log.error e.message
  183. e.backtrace.each do |l|
  184. Origen.log.error l
  185. end
  186. if @options[:compile]
  187. Origen.app.stats.failed_files += 1
  188. else
  189. Origen.app.stats.failed_patterns += 1
  190. end
  191. else
  192. raise
  193. end
  194. end
  195. end
  196. 40 Origen.log.stop_job
  197. 40 Origen.app.current_jobs.pop
  198. end
  199. end
  200. end
  201. end

lib/origen/generator/pattern_finder.rb

73.17% lines covered

82 relevant lines. 60 lines covered and 22 lines missed.
    
  1. 2 module Origen
  2. 2 class Generator
  3. # The pattern finder is responsible for finding patterns in the pattern
  4. # directory, allowing the user to create any number of pattern files
  5. # in any number of sub directories without having to declare them.
  6. 2 class PatternFinder
  7. 2 def find(name, options)
  8. # If the pattern is a fully qualified path to a Ruby file, then just run that:
  9. 29 if File.exist?(name) && name.strip =~ /\.rb$/
  10. 2 return check(name, options)
  11. end
  12. 27 name = File.basename(name)
  13. 27 @requested_pattern = name # Remember what was originally asked for in case
  14. # it needs to be output in an error message
  15. # Strip the prefix if exists
  16. 27 if Origen.config.pattern_prefix && name =~ /^#{Origen.config.pattern_prefix}_/
  17. name.gsub!(/^#{Origen.config.pattern_prefix}_/, '')
  18. end
  19. # Strip the extension if present
  20. 27 name.gsub!(/\.\w+$/, '')
  21. # Strip the postfix if exists
  22. 27 if Origen.config.pattern_postfix && name =~ /_#{Origen.config.pattern_postfix}$/
  23. name.gsub!(/_#{Origen.config.pattern_postfix}$/, '')
  24. end
  25. # Otherwise see what can be found...
  26. 27 return :skip unless proceed_with_pattern?(name) # The application has elected not to run this pattern
  27. 27 pats = matching_patterns(name)
  28. # If the pattern is not found in current plugin and current app then look into other included plugins as well
  29. 27 if pats.size == 0
  30. 1 pats = all_matches(name)
  31. end
  32. 27 if pats.size == 0
  33. # If a pattern can't be found see if it is because the real pattern name is actually
  34. # a substituted value.
  35. # Don't want to do this up front since it is possible that some patterns
  36. # will actually have an explicit value in the name.
  37. 1 translation = Origen.config.pattern_name_translator(name)
  38. # Give the current plugin a go at translating the name if the current application
  39. # has not modified it
  40. 1 if translation == name && Origen.app.plugins.current
  41. translation = Origen.app.plugins.current.config.pattern_name_translator(name)
  42. end
  43. 1 if translation
  44. 1 if translation.is_a?(Hash)
  45. 1 name = translation[:source]
  46. else
  47. name = translation
  48. translation = nil
  49. end
  50. end
  51. 1 return :skip unless proceed_with_pattern?(name) # The application has elected not to run this pattern
  52. 1 pats = matching_patterns(name)
  53. 1 if pats.size == 0
  54. pats = all_matches(name)
  55. end
  56. end
  57. # Last chance see if the supplied name works, this could happen if the user normally
  58. # substitutes the name in before_pattern but here they have a pattern that
  59. # actually includes the bit that is normally sub'd out
  60. 27 if pats.size == 0
  61. pats = matching_patterns(@requested_pattern)
  62. if pats.size == 0
  63. pats = all_matches(@requested_pattern)
  64. end
  65. end
  66. 27 if pats.size == 0
  67. fail "Can't find: #{@requested_pattern}"
  68. 27 elsif pats.size > 1
  69. ambiguous_error(pats)
  70. else
  71. 27 if translation
  72. 1 translation.merge(pattern: check(pats.first, options))
  73. else
  74. 26 check(pats.first, options)
  75. end
  76. end
  77. end
  78. 2 def matching_patterns(name)
  79. # Remove extension in case it is something else, e.g. .atp
  80. 28 name = name.gsub(/\..*$/, '')
  81. 28 matches = []
  82. # First look into the current plugin
  83. 28 if current_plugin_pattern_path
  84. 16 matches = Dir.glob("#{current_plugin_pattern_path}/**/#{name}.rb").sort
  85. # If the current plugin does not include the pattern then look into the current app
  86. 16 if matches.size == 0
  87. 14 matches = Dir.glob(["#{pattern_directory}/**/#{name}.rb", "#{Origen.root}/app/patterns/**/#{name}.rb"]).sort # <= this does not include symlinks
  88. end
  89. else
  90. 12 matches = Dir.glob(["#{pattern_directory}/**/#{name}.rb", "#{Origen.root}/app/patterns/**/#{name}.rb"]).sort # <= this does not include symlinks
  91. end
  92. 28 matches
  93. end
  94. 2 def current_plugin_pattern_path
  95. 44 cp = Origen.app.plugins.current
  96. 44 if cp && cp.config.shared
  97. 32 path = cp.config.shared[:patterns] || cp.config.shared[:pattern]
  98. 32 File.join(cp.root, path) if path
  99. end
  100. end
  101. 2 def all_matches(name)
  102. 1 name = name.gsub(/\..*$/, '')
  103. 1 matches = Dir.glob(["#{pattern_directory}/**{,/*/**}/#{name}.rb", "#{Origen.root}/app/patterns/**{,/*/**}/#{name}.rb"]).sort # Takes symlinks into consideration
  104. 1 matches.flatten.uniq
  105. end
  106. 2 def pattern_directory
  107. 27 Origen.config.pattern_directory
  108. end
  109. # Check with the application that it wishes to run the given pattern
  110. 2 def proceed_with_pattern?(name)
  111. 57 Origen.config.proceed_with_pattern(name)
  112. end
  113. 2 def check(path, options = {})
  114. 29 file_plugin = Origen.app.plugins.plugin_name_from_path(path)
  115. 29 if file_plugin
  116. 4 if Origen.app.plugins.current
  117. 4 if file_plugin == Origen.app.plugins.current.name
  118. 4 return proceed_with_pattern?(path) ? path : :skip
  119. elsif !options[:current_plugin]
  120. Origen.app.plugins.current.temporary = file_plugin
  121. return proceed_with_pattern?(path) ? path : :skip
  122. else
  123. puts "The requested pattern is from plugin #{file_plugin} and current system plugin is set to plugin #{Origen.app.plugins.current.name}!"
  124. fail 'Incorrect plugin error!'
  125. end
  126. else
  127. Origen.app.plugins.current.temporary = file_plugin
  128. return proceed_with_pattern?(path) ? path : :skip
  129. end
  130. else
  131. 25 return proceed_with_pattern?(path) ? path : :skip
  132. end
  133. end
  134. 2 def ambiguous_error(pats)
  135. if Origen.running_locally?
  136. Origen.log.info 'The following patterns match:'
  137. Origen.log.info pats
  138. end
  139. fail "Ambiguous name: #{@requested_pattern}"
  140. end
  141. end
  142. end
  143. end

lib/origen/generator/pattern_iterator.rb

86.67% lines covered

30 relevant lines. 26 lines covered and 4 lines missed.
    
  1. 2 module Origen
  2. 2 class Generator
  3. 2 class PatternIterator
  4. 2 attr_accessor :key
  5. 2 def invoke(options, &block)
  6. 16 if enabled?(options)
  7. 16 @loop.call(options[key], &block)
  8. else
  9. yield
  10. end
  11. end
  12. 2 def loop(&block)
  13. 6 @loop = block
  14. end
  15. 2 def setup(&block)
  16. 122 if block
  17. 2 @setup = block
  18. 120 elsif @setup
  19. 60 @setup
  20. # Setup is optional for an iterator, return something to keep the caller happy
  21. else
  22. 120 ->(arg) { arg }
  23. end
  24. end
  25. 2 def startup(&block)
  26. 120 if block
  27. @startup = block
  28. 120 elsif @startup
  29. @startup
  30. # Startup is optional for an iterator, return something to keep the caller happy
  31. else
  32. 240 ->(_options, arg) { arg }
  33. end
  34. end
  35. 2 def pattern_name(&block)
  36. 126 if block
  37. 6 @pattern_name = block
  38. 120 elsif @pattern_name
  39. 120 @pattern_name
  40. else
  41. fail "pattern_name must be defined for iterator: #{key}"
  42. end
  43. end
  44. 2 def enabled?(options)
  45. 376 options.keys.include?(key)
  46. end
  47. end
  48. end
  49. end

lib/origen/generator/pattern_sequence.rb

95.51% lines covered

156 relevant lines. 149 lines covered and 7 lines missed.
    
  1. 1 require 'io/console'
  2. 1 module Origen
  3. 1 class Generator
  4. # Manages a single pattern sequence, i.e. an instance of PatternSequence is
  5. # created for every Pattern.sequence do ... end block
  6. 1 class PatternSequence
  7. 1 def initialize(name, block, pre_block = nil)
  8. 6 @number_of_threads = 1
  9. 6 @name = name
  10. 6 @running_thread_ids = { main: true }
  11. # The contents of the main Pattern.sequence block will be executed as a thread and treated
  12. # like any other parallel block
  13. 6 thread = PatternThread.new(:main, self, block, true, pre_block)
  14. 6 threads << thread
  15. 6 active_threads << thread
  16. 6 PatSeq.send(:current_sequence=, self)
  17. 6 @sync_ups = {}
  18. end
  19. # Execute the given pattern
  20. 1 def run(pattern_name)
  21. 2 name = Pathname.new(pattern_name.to_s).basename
  22. 2 ss "START OF PATTERN: #{name}"
  23. # Give the app a chance to handle pattern dispatch
  24. 2 skip = false
  25. 2 Origen.app.listeners_for(:before_pattern_lookup).each do |listener|
  26. 2 skip ||= !listener.before_pattern_lookup(pattern_name.to_s)
  27. end
  28. 2 unless skip
  29. 2 pattern = Origen.generator.pattern_finder.find(pattern_name.to_s, {})
  30. 2 pattern = pattern[:pattern] if pattern.is_a?(Hash)
  31. 2 load pattern
  32. end
  33. 2 ss "END OF PATTERN: #{name}"
  34. end
  35. 1 alias_method :call, :run
  36. # Execute the given block in a new concurrent thread
  37. 1 def thread(id = nil, &block)
  38. 13 @number_of_threads += 1
  39. 13 id ||= "thread#{@number_of_threads}".to_sym
  40. # Just stage the request for now, it will be started at the end of the current execute loop
  41. 13 @parallel_blocks_waiting_to_start ||= []
  42. 13 @parallel_blocks_waiting_to_start << [id, block]
  43. 13 @running_thread_ids[id] = true
  44. end
  45. 1 alias_method :in_parallel, :thread
  46. 1 def wait_for_threads_to_complete(*ids)
  47. 3 completed = false
  48. 3 blocked = false
  49. 3 ids = ids.map(&:to_sym)
  50. 3 all = ids.empty? || ids.include?(:all)
  51. 3 until completed
  52. 22 if all
  53. 19 limit = current_thread.id == :main ? 1 : 2
  54. 19 if @running_thread_ids.size > limit
  55. 17 current_thread.waiting_for_thread(blocked)
  56. 17 blocked = true
  57. else
  58. 2 current_thread.record_active if blocked
  59. 2 completed = true
  60. end
  61. else
  62. 6 if ids.any? { |id| @running_thread_ids[id] }
  63. 2 current_thread.waiting_for_thread(blocked)
  64. 2 blocked = true
  65. else
  66. 1 current_thread.record_active if blocked
  67. 1 completed = true
  68. end
  69. end
  70. end
  71. end
  72. 1 alias_method :wait_for_thread, :wait_for_threads_to_complete
  73. 1 alias_method :wait_for_threads, :wait_for_threads_to_complete
  74. 1 alias_method :wait_for_thread_to_complete, :wait_for_threads_to_complete
  75. 1 private
  76. 1 def sync_up(location, *ids)
  77. 5 options = ids.pop if ids.last.is_a?(Hash)
  78. 5 options ||= {}
  79. 5 ids = ids.map(&:to_sym)
  80. 5 if ids.empty? || ids.include?(:all)
  81. 2 ids = @running_thread_ids.keys
  82. 2 ids.delete(:main) unless options[:include_main]
  83. end
  84. # Just continue if this thread is not in the list
  85. 5 return unless ids.include?(current_thread.id)
  86. # If we have entered the same sync up point after having previously completed it,
  87. # then clear it and start again
  88. 4 if @sync_ups[location] && @sync_ups[location][:completed]
  89. @sync_ups[location] = nil
  90. end
  91. # Don't need to worry about race conditions here as Origen only allows 1 thread
  92. # to be active at a time
  93. 4 if @sync_ups[location]
  94. 2 @sync_ups[location][:arrived] << current_thread.id
  95. else
  96. 2 @sync_ups[location] = { required: Set.new, arrived: Set.new, completed: false }
  97. 6 ids.each { |id| @sync_ups[location][:required] << id }
  98. 2 @sync_ups[location][:arrived] << current_thread.id
  99. end
  100. 4 if @sync_ups[location][:required] == @sync_ups[location][:arrived]
  101. 2 @sync_ups[location][:completed] = true
  102. end
  103. 4 blocked = false
  104. 4 until @sync_ups[location][:completed]
  105. 7 current_thread.waiting_for_thread(blocked)
  106. 7 blocked = true
  107. 7 Origen.log.debug "Waiting for sync_up: #{@sync_ups}"
  108. end
  109. 4 current_thread.record_active if blocked
  110. end
  111. 1 def thread_running?(id)
  112. @running_thread_ids[id]
  113. end
  114. 1 def current_thread
  115. 59 PatSeq.thread
  116. end
  117. 1 def log_execution_profile
  118. 6 if threads.size > 1
  119. 25 thread_id_size = threads.map { |t| t.id.to_s.size }.max
  120. begin
  121. 6 line_size = IO.console.winsize[1] - 35 - thread_id_size
  122. rescue
  123. line_size = 150
  124. end
  125. 6 line_size -= 16 if tester.try(:sim?)
  126. 6 cycles_per_tick = (@cycle_count_stop / (line_size * 1.0)).ceil
  127. 6 if tester.try(:sim?)
  128. execution_time = tester.execution_time_in_ns / 1_000_000_000.0
  129. else
  130. 6 execution_time = Origen.app.stats.execution_time_for(Origen.app.current_job.output_pattern)
  131. end
  132. 6 Origen.log.info ''
  133. 6 tick_time = execution_time / line_size
  134. 6 Origen.log.info "Concurrent execution profile (#{pretty_time(tick_time)}/increment):"
  135. 6 Origen.log.info
  136. 6 number_of_ticks = @cycle_count_stop / cycles_per_tick
  137. 6 ticks_per_step = 0
  138. 6 step_size = 0.1.us
  139. 6 while ticks_per_step < 10
  140. 29 step_size = step_size * 10
  141. 29 ticks_per_step = step_size / tick_time
  142. end
  143. 6 ticks_per_step = ticks_per_step.ceil
  144. 6 step_size = tick_time * ticks_per_step
  145. 6 if tester.try(:sim?)
  146. padding = '.' + (' ' * (thread_id_size + 1))
  147. else
  148. 6 padding = ' ' * (thread_id_size + 2)
  149. end
  150. 6 scale_step = '|' + ('-' * (ticks_per_step - 1))
  151. 6 number_of_steps = (number_of_ticks / ticks_per_step) + 1
  152. 6 scale = scale_step * number_of_steps
  153. 6 scale = scale[0, number_of_ticks]
  154. 6 Origen.log.info padding + scale
  155. 6 scale = ''
  156. 6 number_of_steps.times do |i|
  157. 26 scale += pretty_time(i * step_size, 1).ljust(ticks_per_step)
  158. end
  159. 6 scale = scale[0, number_of_ticks]
  160. 6 Origen.log.info padding + scale
  161. 6 threads.each do |thread|
  162. 19 line = thread.execution_profile(0, @cycle_count_stop, cycles_per_tick)
  163. 19 Origen.log.info ''
  164. 19 Origen.log.info "#{thread.id}: ".ljust(thread_id_size + 2) + line
  165. end
  166. 6 Origen.log.info ''
  167. end
  168. end
  169. 1 def pretty_time(time, number_decimal_places = 0)
  170. 32 return '0' if time == 0
  171. 26 if time < 1.us
  172. "%.#{number_decimal_places}fns" % (time * 1_000_000_000)
  173. 26 elsif time < 1.ms
  174. 6 "%.#{number_decimal_places}fus" % (time * 1_000_000)
  175. 20 elsif time < 1.s
  176. 20 "%.#{number_decimal_places}fms" % (time * 1_000)
  177. else
  178. "%.#{number_decimal_places}fs" % tick_time
  179. end
  180. end
  181. 1 def thread_completed(thread)
  182. 19 @running_thread_ids.delete(thread.id)
  183. 19 active_threads.delete(thread)
  184. end
  185. 1 def threads
  186. 43 @threads ||= []
  187. end
  188. 1 def active_threads
  189. 8880 @active_threads ||= []
  190. end
  191. 1 def threads_waiting_to_start?
  192. 2091 @parallel_blocks_waiting_to_start
  193. end
  194. 1 def execute
  195. 6 active_threads.first.start
  196. 6 until active_threads.empty?
  197. # Advance all threads to their next cycle point in sequential order. Keeping tight control of
  198. # when threads are running in this way ensures that the output is deterministic no matter what
  199. # computer it is running on, and ensures that the application code does not have to worry about
  200. # race conditions.
  201. 2949 cycs = active_threads.map do |t|
  202. 5699 t.advance
  203. 5699 t.pending_cycles
  204. end.compact.min
  205. 2949 if cycs
  206. # Now generate the required number of cycles which is defined by the thread that has the least
  207. # amount of cycles ready to go.
  208. # Since tester.cycle is being called by the master process here it will generate as normal (as
  209. # opposed to when called from a thread in which case it causes the thread to wait).
  210. 2932 cycs.cycles
  211. # Now let each thread know how many cycles we just generated, so they can decide whether they
  212. # need to wait for more cycles or if they can start preparing the next one
  213. 8607 active_threads.each { |t| t.executed_cycles(cycs) }
  214. end
  215. 2949 if @parallel_blocks_waiting_to_start
  216. 6 @parallel_blocks_waiting_to_start.each do |id, block|
  217. 13 thread = PatternThread.new(id, self, block)
  218. 13 threads << thread
  219. 13 active_threads << thread
  220. 13 thread.start
  221. end
  222. 6 @parallel_blocks_waiting_to_start = nil
  223. end
  224. end
  225. 6 @cycle_count_stop = threads.first.current_cycle_count
  226. end
  227. end
  228. end
  229. end

lib/origen/generator/pattern_sequencer.rb

96.97% lines covered

66 relevant lines. 64 lines covered and 2 lines missed.
    
  1. 2 require 'concurrent'
  2. 2 module Origen
  3. 2 class Generator
  4. # Provides APIs to enable applications to support concurrency
  5. 2 class PatternSequencer
  6. 2 class << self
  7. 2 def serialize(id = nil)
  8. 30 if active?
  9. 30 s = nil
  10. 30 id ||= caller[0]
  11. 30 @semaphores ||= {}
  12. 30 @semaphores[id] ||= Concurrent::Semaphore.new(1)
  13. 30 s = @semaphores[id]
  14. 30 completed = false
  15. 30 blocked = false
  16. 30 until completed
  17. # If already acquired or available
  18. 751 if (thread.reservations[id] && thread.reservations[id][:semaphore]) || s.try_acquire
  19. 30 thread.record_active if blocked
  20. 30 yield
  21. 30 completed = true
  22. else
  23. 721 thread.waiting_for_serialize(id, blocked)
  24. 721 blocked = true
  25. end
  26. end
  27. # If the thread has reserved access to this serialized resource then don't release it now, but
  28. # store a reference to the semaphore and it will be released at the end of the reserve block
  29. 30 if thread.reservations[id]
  30. 15 thread.reservations[id][:semaphore] = s
  31. else
  32. 15 s.release
  33. end
  34. else
  35. yield
  36. end
  37. end
  38. # Once a lock is acquired on a serialize block with the given ID, it won't be released to
  39. # other parallel threads until the end of this block
  40. 2 def reserve(id)
  41. 10 if active?
  42. 10 if thread.reservations[id]
  43. 5 thread.reservations[id][:count] += 1
  44. else
  45. 5 thread.reservations[id] = { count: 1, semaphore: nil }
  46. end
  47. 10 yield
  48. 10 if thread.reservations[id][:count] == 1
  49. # May not be set if the application reserved the resource but never hit it
  50. 5 if s = thread.reservations[id][:semaphore]
  51. 5 s.release
  52. end
  53. 5 thread.reservations[id] = nil
  54. else
  55. 5 thread.reservations[id][:count] -= 1
  56. end
  57. else
  58. yield
  59. end
  60. end
  61. # Returns true if a pattern sequence is currently open/active
  62. 2 def active?
  63. 5630 !!@active
  64. end
  65. 2 alias_method :open?, :active?
  66. 2 alias_method :runnng?, :active?
  67. # Returns the PatternThread object for the current thread
  68. 2 def thread
  69. 16303 @thread.value
  70. end
  71. # Prepends the given string with "[<current thread ID>] " unless it already contains it
  72. 2 def add_thread(str)
  73. 5590 if active? && thread
  74. 153 id = "[#{thread.id}] "
  75. 153 str.prepend(id) unless str =~ /#{id}/
  76. end
  77. 5590 str
  78. end
  79. # Wait for the given threads to complete. If no IDs given it will wait for all currently running
  80. # threads (except for the one who called this) to complete.
  81. 2 def wait_for_threads_to_complete(*ids)
  82. 3 @current_sequence.wait_for_threads_to_complete(*ids)
  83. end
  84. 2 alias_method :wait_for_thread, :wait_for_threads_to_complete
  85. 2 alias_method :wait_for_threads, :wait_for_threads_to_complete
  86. 2 alias_method :wait_for_thread_to_complete, :wait_for_threads_to_complete
  87. 2 def sync_up(*ids)
  88. 5 if @current_sequence
  89. 5 @current_sequence.send(:sync_up, caller[0], *ids)
  90. end
  91. end
  92. 2 private
  93. 2 def current_sequence=(seq)
  94. 6 @current_sequence = seq
  95. end
  96. 2 def active=(val)
  97. 12 @active = val
  98. end
  99. 2 def thread=(t)
  100. 21 @thread ||= Concurrent::ThreadLocalVar.new(nil)
  101. 21 @thread.value = t
  102. end
  103. end
  104. end
  105. end
  106. end
  107. 2 PatSeq = Origen::Generator::PatternSequencer
  108. 2 PatSeq.send(:thread=, nil)

lib/origen/generator/pattern_thread.rb

96.0% lines covered

100 relevant lines. 96 lines covered and 4 lines missed.
    
  1. 1 module Origen
  2. 1 class Generator
  3. # An instance of PatternThread is created for each parallel thread of execution
  4. # in a pattern sequence. One instance of this class is also created to represent
  5. # the original main thread in addition to those created by calling seq.in_parallel
  6. 1 class PatternThread
  7. # Returns the parent pattern sequence object
  8. 1 attr_reader :sequence
  9. 1 attr_reader :pending_cycles
  10. 1 attr_reader :id
  11. 1 attr_reader :reservations
  12. 1 attr_reader :cycle_count_start
  13. 1 attr_reader :cycle_count_stop
  14. # A record of when the thread is active to construct the execution profile
  15. 1 attr_reader :events
  16. 1 def initialize(id, sequence, block, primary = false, pre_block = nil)
  17. 19 if primary
  18. 6 @cycle_count_start = 0
  19. else
  20. 13 @cycle_count_start = current_cycle_count
  21. end
  22. 19 @events = [[:active, cycle_count_start]]
  23. 19 @id = id.to_sym
  24. 19 @sequence = sequence
  25. 19 @block = block
  26. 19 @pre_block = pre_block
  27. 19 @primary = primary
  28. 19 @running = Concurrent::Event.new
  29. 19 @waiting = Concurrent::Event.new
  30. 19 @pending_cycles = nil
  31. 19 @completed = false
  32. 19 @reservations = {}
  33. end
  34. # Returns true if this is main thread (the one from which all in_parallel threads
  35. # have been branched from)
  36. 1 def primary?
  37. @primary
  38. end
  39. # @api private
  40. #
  41. # This method is called once by the pattern sequence to start a new thread. It will block until
  42. # the thread is in the waiting state.
  43. 1 def start
  44. 19 @thread = Thread.new do
  45. 19 PatSeq.send(:thread=, self)
  46. 19 wait
  47. 19 @pre_block.call if @pre_block
  48. 19 @block.call(sequence)
  49. 19 sequence.send(:thread_completed, self)
  50. 19 record_cycle_count_stop
  51. 19 @completed = true
  52. 19 wait
  53. end
  54. 19 @waiting.wait
  55. end
  56. 1 def record_cycle_count_stop
  57. 19 @cycle_count_stop = current_cycle_count
  58. 19 events << [:stopped, cycle_count_stop]
  59. 19 events.freeze
  60. end
  61. 1 def record_active
  62. 6 events << [:active, current_cycle_count]
  63. end
  64. 1 def current_cycle_count
  65. 50 tester.try(:cycle_count) || 0
  66. end
  67. 1 def execution_profile(start, stop, step)
  68. 19 events = @events.dup
  69. 19 cycles = start
  70. 19 state = :inactive
  71. 19 line = ''
  72. 19 ((stop - start) / step).times do |i|
  73. 5567 active_cycles = 0
  74. 5567 while events.first && events.first[1] >= cycles && events.first[1] < cycles + step
  75. 44 event = events.shift
  76. # Bring the current cycles up to this event point applying the current state
  77. 44 if state == :active
  78. 19 active_cycles += event[1] - cycles
  79. end
  80. 44 state = event[0] == :active ? :active : :inactive
  81. 44 cycles = event[1]
  82. end
  83. # Bring the current cycles up to the end of this profile tick
  84. 5567 if state == :active
  85. 2535 active_cycles += ((i + 1) * step) - cycles
  86. end
  87. 5567 cycles = ((i + 1) * step)
  88. 5567 if active_cycles == 0
  89. 3017 line += '_'
  90. 2550 elsif active_cycles > (step * 0.5)
  91. 2531 line += '█'
  92. else
  93. 19 line += '▄'
  94. end
  95. end
  96. 19 line
  97. end
  98. # Will be called when the thread can't execute its next cycle because it is waiting to obtain a
  99. # lock on a serialized block
  100. 1 def waiting_for_serialize(serialize_id, skip_event = false)
  101. # puts "Thread #{id} is blocked waiting for #{serialize_id}"
  102. 721 events << [:waiting, current_cycle_count] unless skip_event
  103. 721 wait
  104. end
  105. # Will be called when the thread can't execute its next cycle because it is waiting for another
  106. # thread to complete
  107. 1 def waiting_for_thread(skip_event = false)
  108. 26 events << [:waiting, current_cycle_count] unless skip_event
  109. 26 wait
  110. end
  111. # Will be called when the thread is ready for the next cycle
  112. 1 def cycle(options)
  113. 4933 @pending_cycles = options[:repeat] || 1
  114. # If there are threads pending start and we are about to enter a long delay, block for only
  115. # one cycle to give them a change to get underway and make use of this delay
  116. 4933 if @pending_cycles > 1 && sequence.send(:threads_waiting_to_start?)
  117. 3 remainder = @pending_cycles - 1
  118. 3 @pending_cycles = 1
  119. end
  120. 4933 wait
  121. 4933 @pending_cycles = remainder if remainder
  122. # If the sequence did not do enough cycles in that round to satisfy this thread, then go back
  123. # around to complete the remainder before continuing with the rest of the pattern
  124. 4933 if @pending_cycles == 0
  125. 2935 @pending_cycles = nil
  126. 1998 elsif @pending_cycles > 0
  127. 1998 @pending_cycles.cycles
  128. else
  129. fail "Something has gone wrong @pending_cycles is #{@pending_cycles}"
  130. end
  131. end
  132. # @api private
  133. 1 def executed_cycles(cycles)
  134. 5675 @pending_cycles -= cycles if @pending_cycles
  135. end
  136. 1 def completed?
  137. @completed
  138. end
  139. # Returns true if the thread is currently waiting for the pattern sequence to advance it
  140. 1 def waiting?
  141. @waiting.set?
  142. end
  143. # This should be called only by the pattern thread itself, and will block it until it is told to
  144. # advance by the pattern sequence running in the main thread
  145. 1 def wait
  146. 5718 @running.reset
  147. 5718 @waiting.set
  148. 5718 @running.wait
  149. end
  150. # This should be called only by the pattern sequence running in the main thread, it will un-block the
  151. # pattern thread which is currently waiting, and it will block the main thread until the pattern thread
  152. # reaches the next wait point (or completes)
  153. 1 def advance(completed_cycles = nil)
  154. 5699 @waiting.reset
  155. 5699 @running.set # Release the pattern thread
  156. 5699 @waiting.wait # And wait for it to reach the next wait point
  157. end
  158. end
  159. end
  160. end

lib/origen/generator/renderer.rb

68.75% lines covered

64 relevant lines. 44 lines covered and 20 lines missed.
    
  1. 2 module Origen
  2. 2 class Generator
  3. # Handles the recursive rendering and importing of sub templates
  4. # and source files
  5. 2 module Renderer
  6. 2 def render(file, options = {}, &block)
  7. 23 fail 'File argument is nil' unless file
  8. 23 file = Origen.file_handler.clean_path_to_sub_template(file)
  9. 23 current_pipeline << { file: file, options: options,
  10. placeholder: placeholder, block: block,
  11. indent: options[:indent] || 0
  12. }
  13. 23 if block_given?
  14. 5 self.current_buffer += current_pipeline.last[:placeholder] + "\n"
  15. end
  16. 23 current_pipeline.last[:placeholder]
  17. end
  18. 2 alias_method :import, :render
  19. 2 def placeholder
  20. 23 @ix ||= 0
  21. 23 @ix += 1
  22. 23 "_origen_render_placeholder_#{@ix}"
  23. end
  24. 2 def options
  25. 472 @current_options ||= {}
  26. end
  27. 2 def pipeline
  28. 372 @pipeline ||= []
  29. 372 @pipeline << [] if @pipeline.empty?
  30. 372 @pipeline
  31. end
  32. 2 def current_pipeline
  33. 223 pipeline.last
  34. end
  35. # Insert rendered content into any placeholders
  36. 2 def insert(content)
  37. 126 while current_pipeline.size > 0
  38. 23 current = current_pipeline.pop
  39. 23 pipeline << []
  40. 23 @current_options = current[:options]
  41. 23 self.current_buffer = ''
  42. 23 output = compile(current[:file],
  43. sub_template: true,
  44. block: current[:block],
  45. scope: @scope
  46. )
  47. 23 if current[:indent] && current[:indent] > 0
  48. 1 indent = ' ' * current[:indent]
  49. 9 output = output.split("\n").map { |l| indent + l }.join("\n")
  50. end
  51. 23 @current_options = nil
  52. 23 content = insert_content(content, current[:placeholder], output)
  53. end
  54. 126 pipeline.pop
  55. # Always give back a string, this is what existing callers expect
  56. #
  57. # Possible this could in future run into problems if the whole file cannot be read
  58. # into memory, but we can cross that path when we come to it
  59. 126 if content.is_a?(Pathname)
  60. c = content.read
  61. content.delete
  62. c
  63. else
  64. 126 content
  65. end
  66. end
  67. 2 def insert_content(current, placeholder, content)
  68. # Start using the disk for storing the output rather than memory
  69. # once it starts to exceed this length
  70. 23 max_length = 1_000_000
  71. 23 if current.is_a?(Pathname) || content.is_a?(Pathname) ||
  72. 23 ((current.length + content.length) > max_length)
  73. unless current.is_a?(Pathname)
  74. t = temporary_file
  75. t.open('w') { |f| f.puts current }
  76. current = t
  77. end
  78. new = temporary_file
  79. new.open('w') do |new_f|
  80. current.each_line do |line|
  81. if line.strip == placeholder
  82. if content.is_a?(Pathname)
  83. content.each_line do |line|
  84. new_f.puts line
  85. end
  86. content.delete
  87. else
  88. new_f.puts content.chomp
  89. end
  90. else
  91. new_f.puts line
  92. end
  93. end
  94. end
  95. current.delete
  96. new
  97. else
  98. 23 current.sub(/ *#{placeholder}/, content)
  99. end
  100. end
  101. # Returns a Pathname to a uniquely named temporary file
  102. 2 def temporary_file
  103. # Ensure this is unique so that is doesn't clash with parallel compile processes
  104. Pathname.new "#{Origen.root}/tmp/compiler_#{Process.pid}_#{Time.now.to_f}"
  105. end
  106. end
  107. end
  108. end

lib/origen/generator/stage.rb

92.68% lines covered

41 relevant lines. 38 lines covered and 3 lines missed.
    
  1. 2 module Origen
  2. 2 class Generator
  3. # The stage provides a way to store objects in named banks for later retrieval.
  4. # This is typically used during pattern generation to generate header, body and
  5. # footer elements of a pattern in non-sequential order, allowing them to be
  6. # combined at the end into the logical order.
  7. 2 class Stage
  8. 2 def initialize
  9. 2 @vault = {}
  10. end
  11. 2 def reset!
  12. 95 @vault = {}
  13. end
  14. # Returns vectors from the end of the bank
  15. 2 def last_vector(offset = 0)
  16. 215 offset = offset.abs
  17. 215 i = current_bank.size - 1
  18. 215 while offset >= 0
  19. 435 return nil if i < 0
  20. 423 unless current_bank[i].is_a?(String)
  21. 247 return current_bank[i] if offset == 0
  22. 44 offset -= 1
  23. end
  24. 220 i -= 1
  25. end
  26. end
  27. # Same as last_vector except it returns the last objects of any
  28. # type, not just vectors
  29. 2 def last_object(offset = 0)
  30. 4 i = current_bank.size - 1 - offset
  31. 4 current_bank[i]
  32. end
  33. # Store a new value in the current bank
  34. 2 def store(obj)
  35. 10669 current_bank.push(obj)
  36. end
  37. # Insert a new object into the current bank X places from the end
  38. 2 def insert_from_end(obj, x)
  39. # Ruby insert is a bit un-intuative in that insert(1) will insert something 1 place in from the
  40. # start, whereas insert(-1) will insert it at the end (0 places in from the end).
  41. # So the subtraction of 1 here aligns the behavior when inserting from the start or the end.
  42. 2 current_bank.insert((x * -1) - 1, obj)
  43. end
  44. # Insert a new object into the current bank X places from the start
  45. 2 def insert_from_start(obj, x)
  46. current_bank.insert(x, obj)
  47. end
  48. # Pull the last item added to the current bank
  49. 2 def newest
  50. current_bank.pop
  51. end
  52. # Pull the oldest item added to the current bank
  53. 2 def oldest
  54. current_bank.shift
  55. end
  56. # Set the current bank
  57. 2 def bank=(name)
  58. 95 @bank = name
  59. end
  60. # Returns the entire bank, an array
  61. 2 def bank(name = @bank)
  62. 264 @vault[name] || []
  63. end
  64. 2 def current_bank
  65. 11524 return @vault[@bank] if @vault[@bank]
  66. 284 @vault[@bank] = []
  67. end
  68. # Temporarily switches to the given bank
  69. 2 def with_bank(bank)
  70. 285 orig_bank = @bank
  71. 285 @bank = bank
  72. 285 yield
  73. 285 @bank = orig_bank
  74. end
  75. end
  76. end
  77. end

lib/origen/location.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. 1 module Origen
  2. 1 module Location
  3. 1 autoload :Map, 'origen/location/map'
  4. 1 autoload :Base, 'origen/location/base'
  5. end
  6. end

lib/origen/location/base.rb

86.36% lines covered

66 relevant lines. 57 lines covered and 9 lines missed.
    
  1. 1 module Origen
  2. 1 module Location
  3. # A Location is an abstract object used to represent any NVM location
  4. # of interest, such as a pass code, security field, etc.
  5. 1 class Base
  6. 1 attr_accessor :address, :endian, :size_in_bytes, :owner
  7. 1 alias_method :byte_address, :address
  8. 1 alias_method :byte_aligned_byte_address, :address
  9. 1 alias_method :endianess, :endian
  10. 1 def initialize(options = {})
  11. options = {
  12. 19 size_in_bytes: 1,
  13. word_size_in_bytes: 2,
  14. endian: :big,
  15. data: 0,
  16. nil_state: 0
  17. }.merge(options)
  18. 19 @address = options.delete(:address) || options.delete(:byte_address)
  19. 19 @endian = options.delete(:endian)
  20. 19 @size_in_bytes = options.delete(:size_in_bytes)
  21. 19 @nil_state = options.delete(:nil_state)
  22. 19 @owner = options.delete(:owner)
  23. 19 write(options.delete(:data), size_in_bytes: @size_in_bytes)
  24. 19 create_address_methods(options)
  25. end
  26. 1 def aligned_address(bytes)
  27. 6 f = bytes - 1
  28. 6 (address >> f) << f
  29. end
  30. 1 def big_endian?
  31. 43 endian == :big
  32. end
  33. 1 def little_endian?
  34. endian == :little
  35. end
  36. 1 def write(data, options = {})
  37. 27 @current_data = data
  38. 27 @current_data_size_in_bytes = options[:size_in_bytes] || size_in_bytes
  39. 27 self.data(options)
  40. end
  41. 1 alias_method :set, :write
  42. 1 def data(options = {})
  43. 42 data = @current_data
  44. 42 nil_val = options[:nil_state] || @nil_state
  45. 42 shift = 8 * (size_in_bytes - @current_data_size_in_bytes)
  46. 42 mask = (1 << shift) - 1
  47. 42 if big_endian?
  48. 32 data <<= shift
  49. 32 if nil_val == 1 && shift != 0
  50. 2 data |= mask
  51. end
  52. else
  53. 10 if nil_val == 1
  54. 5 data |= (mask << shift)
  55. end
  56. end
  57. 42 data
  58. end
  59. 1 alias_method :value, :data
  60. 1 alias_method :val, :data
  61. 1 def read!(*args)
  62. action!(:read, *args)
  63. end
  64. 1 def write!
  65. action!(:write, *args)
  66. end
  67. 1 def store!
  68. action!(:store, *args)
  69. end
  70. 1 def program!
  71. action!(:program, *args)
  72. end
  73. 1 def erase!
  74. action!(:erase, *args)
  75. end
  76. 1 private
  77. 1 def action!(type, *args)
  78. if owner
  79. owner.send(type, self, *args)
  80. else
  81. fail "To #{type} a location an owner must be assigned to it!"
  82. end
  83. end
  84. 1 def create_address_methods(options)
  85. 19 options.each do |key, value|
  86. 21 if key.to_s =~ /(\w+)_size_in_bytes$/
  87. 21 define_singleton_method("#{Regexp.last_match[1].downcase}_aligned_address") do
  88. 3 aligned_address(value)
  89. end
  90. 21 define_singleton_method("#{Regexp.last_match[1].downcase}_aligned_byte_address") do
  91. 3 aligned_address(value)
  92. end
  93. 21 define_singleton_method("#{Regexp.last_match[1].downcase}_address") do
  94. 3 address / value
  95. end
  96. end
  97. end
  98. end
  99. end
  100. end
  101. end

lib/origen/location/map.rb

95.56% lines covered

45 relevant lines. 43 lines covered and 2 lines missed.
    
  1. 1 require 'active_support/concern'
  2. 1 module Origen
  3. 1 module Location
  4. 1 module Map
  5. 1 extend ActiveSupport::Concern
  6. 1 module ClassMethods
  7. 1 def define_locations(defaults = {})
  8. 4 @x = @x ? (@x + 1) : 0 # Provides a unique ID for each define_locations block
  9. 4 default_attributes[@x] = defaults
  10. 4 @defining = true
  11. 4 yield
  12. 4 @defining = false
  13. end
  14. 1 def constructor(&block)
  15. 2 if defining?
  16. 1 constructors[@x] = block
  17. else
  18. 1 constructors[:default] = block
  19. end
  20. end
  21. 1 def default_constructor(attributes, defaults)
  22. 6 Origen::Location::Base.new(defaults.merge(attributes))
  23. end
  24. 1 def definitions
  25. 18 @definitions ||= {}
  26. end
  27. 1 def constructors
  28. 12 @constructors ||= {}
  29. end
  30. 1 def default_attributes
  31. 10 @default_attributes ||= {}
  32. end
  33. 1 def defining?
  34. 8 @defining
  35. end
  36. # A hash of constructed location objects, i.e. an entry will be cached here the first time a location
  37. # is referenced outside of its initial definition, after that it will be served directly from here.
  38. 1 def constructed
  39. 16 @constructed ||= {}
  40. end
  41. # Provides accessors for all named locations, for example:
  42. #
  43. # $dut.nvm.fmu.ifr_map.probe1_pass
  44. 1 def method_missing(method, *args, &block)
  45. 6 if defining?
  46. 6 if definitions[method]
  47. warning "Redefinition of map location: #{method}"
  48. end
  49. 6 definitions[method] = { attributes: args.first, x: @x }
  50. else
  51. super
  52. end
  53. end
  54. end
  55. 1 def method_missing(method, *args, &block)
  56. 10 klass = self.class
  57. 10 klass.constructed[method] || begin
  58. 6 definition = klass.definitions[method]
  59. 6 if definition
  60. 6 defaults = klass.default_attributes[definition[:x]] || {}
  61. 6 constructor = klass.constructors[definition[:x]] || klass.constructors[:default]
  62. 6 if constructor
  63. 3 instance = constructor.call(definition[:attributes], defaults)
  64. else
  65. 3 instance = klass.default_constructor(definition[:attributes], defaults)
  66. end
  67. 6 klass.constructed[method] = instance
  68. end
  69. end || super
  70. end
  71. end
  72. end
  73. end

lib/origen/mode.rb

96.88% lines covered

32 relevant lines. 31 lines covered and 1 lines missed.
    
  1. 2 module Origen
  2. # A class to handle the Origen execution mode
  3. 2 class Mode
  4. 2 MODES = [:production, :debug, :simulation]
  5. 2 def initialize(_options = {})
  6. 3 @current_mode = :production
  7. end
  8. # When called any future changes to the mode will be ignored
  9. 2 def freeze
  10. 1 @frozen = true
  11. end
  12. 2 def unfreeze
  13. 1 @frozen = false
  14. end
  15. 2 def set(val)
  16. 727 @current_mode = find_mode(val) unless @frozen
  17. end
  18. 2 def to_s
  19. 97 @current_mode ? @current_mode.to_s : ''
  20. end
  21. 2 def find_mode(name)
  22. 726 name = name.to_s.downcase.to_sym
  23. 726 if MODES.include?(name)
  24. 721 name
  25. else
  26. 5 mode = MODES.find do |m|
  27. 12 m.to_s =~ /^#{name}/
  28. end
  29. 5 if mode
  30. 3 mode
  31. else
  32. 2 fail "Invalid mode requested, must be one of: #{MODES}"
  33. end
  34. end
  35. end
  36. # Any mode which is not production will return true here, if
  37. # you want to test for only debug mode use Origen.mode == :debug
  38. 2 def debug?
  39. 6 !production?
  40. end
  41. 2 def production?
  42. 565 @current_mode == :production
  43. end
  44. 2 def simulation?
  45. 367 @current_mode == :simulation
  46. end
  47. 2 def ==(val)
  48. 12 if val.is_a?(Symbol)
  49. 12 @current_mode == val
  50. else
  51. super
  52. end
  53. end
  54. end
  55. end

lib/origen/models.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. 1 module Origen
  2. 1 module Models
  3. 1 autoload :ScanRegister, 'origen/models/scan_register'
  4. 1 autoload :Mux, 'origen/models/mux'
  5. end
  6. end

lib/origen/models/mux.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. 1 module Origen
  2. 1 module Models
  3. 1 class Mux
  4. 1 include Origen::Model
  5. 1 attr_reader :size
  6. 1 attr_reader :select_lines
  7. 1 def initialize(options = {})
  8. 2 @input = []
  9. 2 (2**select_lines).times do |i|
  10. 8 @input << port("input#{i}".to_sym, size: size)
  11. end
  12. 2 port :select, size: select_lines
  13. 2 port :output, size: size
  14. 2 output.connect_to do |i|
  15. 40 unless ports[:select].data.undefined?
  16. 32 send("input#{ports[:select].data}")[i].path
  17. end
  18. end
  19. end
  20. end
  21. end
  22. end

lib/origen/models/scan_register.rb

100.0% lines covered

46 relevant lines. 46 lines covered and 0 lines missed.
    
  1. 1 module Origen
  2. 1 module Models
  3. 1 class ScanRegister
  4. 1 include Origen::Model
  5. 1 attr_reader :size
  6. 1 def initialize(options = {})
  7. # The shift register
  8. 6 reg :sr, 0, size: size, reset: options[:reset] || 0
  9. # The update register, this is the value presented to the outside world
  10. 6 reg :ur, 0, size: size, reset: options[:reset] || 0
  11. 6 port :si # Scan in
  12. 6 port :so # Scan out
  13. 6 port :c, size: size # Capture in
  14. # Control signals
  15. 6 port :se # Shift enable
  16. 6 port :ce # Capture enable
  17. 6 port :ue # Update enable
  18. 6 so.connect_to(sr[0])
  19. end
  20. 1 def method_missing(method, *args, &block)
  21. 266 if BitCollection.instance_methods.include?(method)
  22. 4 ur.send(method, *args, &block)
  23. else
  24. 262 super
  25. end
  26. end
  27. 1 def respond_to?(*args)
  28. 259 super(*args) || BitCollection.instance_methods.include?(args.first)
  29. end
  30. 1 def mode
  31. 32 if se.data == 1
  32. 25 :shift
  33. 7 elsif ce.data == 1
  34. 1 :capture
  35. 6 elsif ue.data == 1
  36. 1 :update
  37. else
  38. 5 undefined
  39. end
  40. end
  41. 1 def clock_prepare
  42. 32 @mode = mode
  43. 32 if @mode == :shift
  44. 25 @din = si.data
  45. 7 elsif @mode == :capture
  46. 1 @din = c.data
  47. 6 elsif @mode == :update
  48. 1 @din = sr.data
  49. end
  50. end
  51. 1 def clock_apply
  52. 32 if @mode == :shift
  53. 25 sr.shift_right(@din)
  54. 7 elsif @mode == :capture
  55. 1 sr.write(@din)
  56. 6 elsif @mode == :update
  57. 1 ur.write(@din)
  58. end
  59. 32 @din = nil
  60. 32 @mode = nil
  61. end
  62. end
  63. end
  64. end

lib/origen/netlist/connectable.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Netlist
  3. 2 module Connectable
  4. 2 extend ActiveSupport::Concern
  5. 2 included do
  6. 6 include Origen::Netlist
  7. end
  8. 2 def connect_to(node = nil, options = {}, &block)
  9. 33 node, options = nil, node if node.is_a?(Hash)
  10. 33 node = node.path if node.respond_to?(:path)
  11. 33 netlist.connect(path, node, &block)
  12. end
  13. 2 alias_method :connect, :connect_to
  14. end
  15. end
  16. end

lib/origen/netlist/list.rb

93.48% lines covered

92 relevant lines. 86 lines covered and 6 lines missed.
    
  1. 2 module Origen
  2. 2 module Netlist
  3. 2 class List
  4. 2 attr_reader :top_level, :table
  5. 2 alias_method :parent, :top_level
  6. 2 alias_method :owner, :top_level
  7. 2 def initialize(top_level)
  8. 14 @top_level = top_level
  9. 14 @table = {}
  10. end
  11. # Connect two paths together in the netlist, one can be a numeric
  12. # value to represent a logic level connection
  13. 2 def connect(a, b = nil, &block)
  14. 33 b ||= block
  15. 33 align(a, b) do |path, index, target|
  16. 71 table[path] ||= {}
  17. 71 table[path][index] ||= []
  18. 71 table[path][index] << target
  19. end
  20. end
  21. 2 def data_bit(path, index, options = {})
  22. 385 bits = data_bits(path, index, options)
  23. 385 if bits.size > 1
  24. fail "Multiple data bit connections found for node #{path}[#{index}]"
  25. 385 elsif bits.size == 0
  26. 38 return undefined
  27. end
  28. 347 bits.first
  29. end
  30. 2 def data_bits(path, index, options = {})
  31. 846 processed_paths = options[:processed_paths] || []
  32. 846 bits = []
  33. 846 ['*', index].each do |i|
  34. 1692 unless processed_paths.include?("#{path}[#{i}]")
  35. 1004 processed_paths << "#{path}[#{i}]"
  36. 1004 vals = (table[path] || {})[i] || []
  37. # Also consider anything attached directly to the requested path, e.g. a
  38. # drive value applied to a port
  39. 1004 vals << "#{path}[#{i}]" if i != '*' && !options[:sublevel]
  40. 1004 vals.each do |val|
  41. 816 if val.is_a?(Proc)
  42. 72 from_proc = true
  43. 72 val = val.call(index)
  44. else
  45. 744 from_proc = false
  46. end
  47. 816 if val.is_a?(Integer)
  48. 57 bits << Registers::Bit.new(nil, index, access: :ro, data: (i == '*' && !from_proc) ? val[index] : val)
  49. 759 elsif val
  50. 751 vp, vi = *to_v(val)
  51. 751 bc = eval("top_level.#{vp}[#{vi || index}]")
  52. 751 if bc.is_a?(Registers::BitCollection)
  53. 52 bits << bc.bit
  54. 699 elsif bc.is_a?(Ports::Section) && bc.drive_value
  55. 238 bits << Registers::Bit.new(nil, index, access: :ro, data: bc.drive_value)
  56. else
  57. 461 bits += data_bits(vp, vi || index, processed_paths: processed_paths, sublevel: true) || []
  58. end
  59. end
  60. end
  61. end
  62. end
  63. 846 bits.uniq
  64. end
  65. 2 private
  66. 2 def align(a, b)
  67. 33 a, b = clean(a), clean(b)
  68. 33 if a[:size] || b[:size]
  69. 8 if a[:size] && b[:size]
  70. 2 size = [a[:size], b[:size]].min
  71. else
  72. 6 size = a[:size] || b[:size]
  73. end
  74. 8 unless a[:numeric] || a[:proc]
  75. 8 size.times do |i|
  76. 14 index = a[:indexes] ? a[:indexes][i] : i
  77. 14 if b[:numeric]
  78. target = b[:path][i]
  79. 14 elsif b[:proc]
  80. target = b[:path]
  81. else
  82. 14 if b[:indexes]
  83. 14 target = "#{b[:path]}[#{b[:indexes][i]}]"
  84. else
  85. target = "#{b[:path]}[#{i}]"
  86. end
  87. end
  88. 14 yield a[:path], index, target
  89. end
  90. end
  91. 8 unless b[:numeric] || b[:proc]
  92. 8 size.times do |i|
  93. 14 index = b[:indexes] ? b[:indexes][i] : i
  94. 14 if a[:numeric]
  95. target = a[:path][i]
  96. 14 elsif a[:proc]
  97. target = a[:path]
  98. else
  99. 14 if a[:indexes]
  100. 8 target = "#{a[:path]}[#{a[:indexes][i]}]"
  101. else
  102. 6 target = "#{a[:path]}[#{i}]"
  103. end
  104. end
  105. 14 yield b[:path], index, target
  106. end
  107. end
  108. else
  109. 25 yield a[:path], '*', b[:path] unless a[:numeric] || a[:proc]
  110. 25 yield b[:path], '*', a[:path] unless b[:numeric] || b[:proc]
  111. end
  112. end
  113. 2 def clean(path)
  114. 66 if path.is_a?(Integer)
  115. 3 { path: path, numeric: true }
  116. 63 elsif path.is_a?(Proc)
  117. 4 { path: path, proc: true }
  118. else
  119. 59 if path =~ /(.*)\[(\d+):?(\d*)\]$/
  120. 10 if Regexp.last_match(3).empty?
  121. 6 { path: Regexp.last_match(1), size: 1, indexes: [Regexp.last_match(2).to_i] }
  122. else
  123. 4 a = ((Regexp.last_match(2).to_i)..(Regexp.last_match(3).to_i)).to_a
  124. 4 { path: Regexp.last_match(1), size: a.size, indexes: a }
  125. end
  126. else
  127. 49 { path: path }
  128. end
  129. end
  130. end
  131. 2 def to_v(path)
  132. 751 if path =~ /(.*)\[(\d+)\]$/
  133. 387 [Regexp.last_match(1), Regexp.last_match(2).to_i]
  134. else
  135. 364 [path, nil]
  136. end
  137. end
  138. end
  139. end
  140. end

lib/origen/org_file.rb

28.95% lines covered

76 relevant lines. 22 lines covered and 54 lines missed.
    
  1. 2 module Origen
  2. 2 class OrgFile
  3. 2 autoload :Interceptor, 'origen/org_file/interceptor'
  4. 2 autoload :Interceptable, 'origen/org_file/interceptable'
  5. 2 class << self
  6. 2 def new(*args, &block)
  7. if @internal_new
  8. super
  9. else
  10. open(*args, &block)
  11. end
  12. end
  13. 2 def open(id, options = {})
  14. fail "An org_file is already open with id: #{id}" if open_files[id]
  15. @internal_new = true
  16. f = OrgFile.new(id, options)
  17. @internal_new = nil
  18. open_files[id] = f
  19. if block_given?
  20. yield f
  21. close(id, options)
  22. else
  23. f
  24. end
  25. end
  26. 2 def close(id, options = {})
  27. fail "An org_file with this ID has not been opened id: #{id}" unless open_files[id]
  28. open_files[id].close unless options[:_internal_org_file_call_]
  29. open_files.delete(id)
  30. end
  31. 2 def org_file(id = nil)
  32. id ? open_files[id] : open_files.to_a.last[1]
  33. end
  34. 2 def cycle(number = 1)
  35. open_files.each { |id, f| f.cycle(number) }
  36. end
  37. 2 private
  38. 2 def open_files
  39. @open_files ||= {}.with_indifferent_access
  40. end
  41. end
  42. 2 attr_accessor :path, :filename
  43. 2 attr_reader :id, :operation
  44. 2 def initialize(id, options = {})
  45. @id = id
  46. @path = options[:path] || default_filepath
  47. @filename = options[:filename] || "#{id}.org"
  48. FileUtils.mkdir_p(path)
  49. @path_to_file = File.join(path, filename)
  50. end
  51. 2 def default_filepath
  52. "#{Origen.root}/pattern/org/#{Origen.target.name}"
  53. end
  54. 2 def file
  55. @file ||= begin
  56. if operation == 'r'
  57. fail "No org file found at: #{@path_to_file}" unless exist?
  58. end
  59. File.open(@path_to_file, operation)
  60. end
  61. end
  62. 2 def exist?
  63. File.exist?(@path_to_file)
  64. end
  65. 2 def close
  66. # Corner case, if no call to read_line or record was made then no file was created and there
  67. # is no file to close
  68. if @file
  69. file.puts "#{@buffer}#{@buffer_cycles}" if @buffer
  70. file.close
  71. end
  72. self.class.close(id, _internal_org_file_call_: true)
  73. nil
  74. end
  75. 2 def read_line
  76. @operation ||= 'r'
  77. operations = file.readline.strip.split(';')
  78. cycles = operations.pop.to_i
  79. operations = operations.map { |op| op.split(',') }.map { |op| op[0] = eval(op[0]); op }
  80. if block_given?
  81. yield operations, cycles
  82. else
  83. [operations, cycles]
  84. end
  85. end
  86. 2 def record(path_to_object, method, *args)
  87. @operation ||= 'w'
  88. @line ||= ''
  89. @line += "#{path_to_object},#{method}"
  90. @line += ",#{args.join(',')}" unless args.empty?
  91. @line += ';'
  92. end
  93. 2 def cycle(number)
  94. if @buffer
  95. if @line == @buffer
  96. @buffer_cycles += number
  97. else
  98. file.puts "#{@buffer}#{@buffer_cycles}"
  99. @buffer = @line
  100. @buffer_cycles = number
  101. end
  102. else
  103. @buffer = @line
  104. @buffer_cycles = number
  105. end
  106. @line = nil
  107. end
  108. end
  109. end

lib/origen/org_file/interceptable.rb

68.0% lines covered

25 relevant lines. 17 lines covered and 8 lines missed.
    
  1. 2 module Origen
  2. 2 class OrgFile
  3. 2 module Interceptable
  4. 2 def self.included(base)
  5. 4 base.extend ClassMethods
  6. end
  7. 2 module ClassMethods
  8. 2 def new(*args, &block)
  9. 8774 o = allocate
  10. 8774 i = OrgFile::Interceptor.new(o)
  11. 8774 o.__interceptor__ = i
  12. 8774 i.send(:initialize, *args, &block)
  13. 8774 unless o.respond_to?(:global_path_to)
  14. puts 'When adding the OrgFile::Interceptable module to a class, the class must define an instance method called "global_path_to", like this:'
  15. puts
  16. puts ' # Must return a string that contains a global path to access the given object,'
  17. puts ' # here for example if the object was a pin'
  18. puts ' def global_path_to'
  19. puts ' "dut.pins(:#{id})"'
  20. puts ' end'
  21. fail "Incomplete integration of OrgFile::Interceptable in #{o.class}"
  22. end
  23. 8774 i
  24. end
  25. end
  26. # Class which include OrgFile::Interceptor should use 'myself' anytime then want to reference 'self',
  27. # this ensures that there are never any references to the unwrapped object
  28. 2 def myself
  29. 26920 @__interceptor__
  30. end
  31. # @api private
  32. 2 def __interceptor__=(obj)
  33. 8774 @__interceptor__ = obj
  34. end
  35. end
  36. end
  37. end

lib/origen/org_file/interceptor.rb

55.1% lines covered

49 relevant lines. 27 lines covered and 22 lines missed.
    
  1. 2 require 'set'
  2. 2 module Origen
  3. 2 class OrgFile
  4. # @api private
  5. #
  6. # Helper for the Interceptor where block_given? doesn't work internally
  7. 2 def self._block_given_?(&block)
  8. block_given?
  9. end
  10. 2 class Interceptor < ::BasicObject
  11. 2 def initialize(object, options = {})
  12. 8774 @object = object
  13. 8774 @@locked = false unless defined? @@locked
  14. end
  15. 2 def inspect(*args)
  16. @object.inspect(*args)
  17. end
  18. 2 def ==(obj)
  19. 95831 if obj.respond_to?(:__org_file_interceptor__)
  20. 95757 @object == obj.__object__
  21. else
  22. 74 @object == obj
  23. end
  24. end
  25. 2 alias_method :equal?, :==
  26. 2 def record_to_org_file(id = nil, options = {})
  27. id, options = nil, id if id.is_a?(::Hash)
  28. if options[:only]
  29. @org_file_methods_to_intercept = Array(options[:only]).to_set
  30. else
  31. @org_file_methods_to_intercept = default_org_file_captures
  32. if options[:also]
  33. @org_file_methods_to_intercept += Array(options[:also]).to_set
  34. end
  35. end
  36. @org_file ||= @old_org_file || OrgFile.org_file(id)
  37. end
  38. # Temporarily stop recording operations within the given block, or stop recording
  39. # completely if no block given
  40. 2 def stop_recording_to_org_file(&block)
  41. @old_org_file = @org_file
  42. if OrgFile._block_given_?(&block)
  43. @org_file = nil
  44. yield
  45. @org_file = @old_org_file
  46. else
  47. @org_file = nil
  48. end
  49. end
  50. 2 def method_missing(method, *args, &block)
  51. 585184 if !@@locked && @org_file && @org_file_methods_to_intercept.include?(method)
  52. @org_file.record(@object.global_path_to, method, *args)
  53. # Locking prevents an operation on an intercepted container object trigger from generating multiple
  54. # org file entries if its contained objects are also intercepted. e.g. Imagine this is a pin group, we
  55. # want the org file to reflect the operation called on the pin group, but not the many subsequent internal
  56. # operations as the group proxies the operation to its contained pins
  57. @@locked = true
  58. @object.send(method, *args, &block)
  59. @@locked = false
  60. else
  61. 585184 @object.send(method, *args, &block)
  62. end
  63. end
  64. 2 def respond_to?(method, include_private = false)
  65. 95900 method == :__org_file_interceptor__ ||
  66. @object.respond_to?(method, include_private)
  67. end
  68. 2 def __org_file_interceptor__
  69. true
  70. end
  71. # @api private
  72. #
  73. # Don't ever use this! An un-wrapped reference to an object must never make it into
  74. # application code or else any operations called on the un-wrapped reference will not
  75. # be captured.
  76. 2 def __object__
  77. 95757 @object
  78. end
  79. 2 private
  80. 2 def debugger
  81. ::Kernel.debugger
  82. end
  83. 2 def default_org_file_captures
  84. @default_captures ||= Array(@object.try(:org_file_intercepted_methods)).to_set
  85. end
  86. end
  87. end
  88. end

lib/origen/parameters/live.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Parameters
  3. 2 require 'delegate'
  4. 2 class Live < ::Delegator
  5. 2 def initialize(options)
  6. 31 @owner = options[:owner]
  7. 31 @path = options[:path].split('.')
  8. 31 @name = options[:name]
  9. end
  10. 2 def __getobj__
  11. 136 p = @owner.params
  12. 275 @path.each { |pt| p = p.send(pt) }
  13. 136 p.send(@name)
  14. end
  15. 2 def is_a_live_parameter?
  16. 3 true
  17. end
  18. end
  19. end
  20. end

lib/origen/parameters/missing.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Parameters
  3. # An instance of this class is returned whenever the parameter context is set to
  4. # a value for which no parameter set has been defined.
  5. #
  6. # Sometime this may be valid in the case where the context is actually implemented
  7. # by another object which shadows the context.
  8. #
  9. # The missing allows the user to do params.context to retrieve the value of the
  10. # current context, but it will error out with a useful error message if they try
  11. # to do anything else (i.e. retrieve a parameter from it)
  12. 2 class Missing
  13. 2 attr_reader :owner
  14. 2 def initialize(options = {})
  15. 2 @owner = options[:owner]
  16. end
  17. 2 def context
  18. 2 owner._parameter_current
  19. end
  20. 2 def method_missing(_method, *_args, &_block)
  21. 1 owner.send(:_validate_parameter_set_name, context)
  22. end
  23. end
  24. end
  25. end

lib/origen/parameters/set.rb

98.08% lines covered

104 relevant lines. 102 lines covered and 2 lines missed.
    
  1. 2 module Origen
  2. 2 module Parameters
  3. 2 class Set < Hash
  4. 2 attr_accessor :top_level
  5. 2 attr_accessor :name
  6. 2 attr_accessor :path
  7. # Allow these parameter names to be valid. When used, they will override the
  8. # methods of the same name provided by the Hash class.
  9. 2 OVERRIDE_HASH_METHODS = [:min, :max]
  10. # Allow these parameter names to be valid. When used, they will override the
  11. # methods of the same name provided by another class.
  12. 2 OVERRIDE_METHODS = [:chain]
  13. 2 def initialize(options = {})
  14. 1284 if options[:top_level]
  15. 308 @top_level = self
  16. 308 @path = ''
  17. 308 @owner = options[:owner]
  18. end
  19. end
  20. 2 def define(parent = nil, &_block)
  21. 320 @defining = true
  22. 320 yield self, parent
  23. 320 @defining = false
  24. 320 finalize unless Origen::Parameters.transaction_open
  25. end
  26. # Returns the current parameter context
  27. 2 def context
  28. 5 owner._parameter_current
  29. end
  30. 2 alias_method :current_context, :context
  31. 2 def available_contexts
  32. 4 owner._parameter_sets.keys
  33. end
  34. 2 alias_method :contexts, :available_contexts
  35. 2 def copy_defaults_from(set)
  36. 992 set.each do |name, val|
  37. 2509 if val.is_a?(Set)
  38. 790 self[name] ||= new_subset(name)
  39. 790 self[name].copy_defaults_from(val)
  40. else
  41. 1719 self[name] = val
  42. end
  43. end
  44. end
  45. 2 def method_missing(method, *args, &block)
  46. 2232 if defining?
  47. 1647 if args.length == 0
  48. 675 self[method] ||= new_subset(method)
  49. 972 elsif args.length > 1
  50. super
  51. else
  52. 972 m = method.to_s.sub('=', '').to_sym
  53. 972 self[m] = args.first
  54. end
  55. else
  56. 585 if args.length != 0
  57. 6 super
  58. else
  59. 579 if !key?(method)
  60. nil
  61. else
  62. 576 val = self[method]
  63. 576 if val.is_a?(Set)
  64. 235 val
  65. else
  66. 341 if live?
  67. 31 Live.new(owner: owner, path: path, name: method)
  68. else
  69. 310 if val.is_a?(Proc)
  70. val.call(*args)
  71. else
  72. 310 val
  73. end
  74. end
  75. end
  76. end
  77. end
  78. end
  79. end
  80. 2 (OVERRIDE_METHODS + OVERRIDE_HASH_METHODS).each do |method|
  81. 6 define_method method do
  82. 27 method_missing(method)
  83. end
  84. end
  85. 2 def each
  86. 2273 super do |key, val|
  87. 5728 if val.is_a?(Proc)
  88. 228 yield key, val.call
  89. else
  90. 5500 yield key, val
  91. end
  92. end
  93. end
  94. 2 def [](key)
  95. 2832 val = super
  96. 2832 val.is_a?(Proc) ? val.call : val
  97. end
  98. # Test seems to be some kind of reserved word, that doesn't trigger the method_missing,
  99. # so re-defining it here to allow a param group called 'test'
  100. 2 def test(*args, &block)
  101. 172 method_missing(:test, *args, &block)
  102. end
  103. 2 def defining?
  104. 3056 return true if Origen::Parameters.transaction_open
  105. 2850 if top_level?
  106. 2026 @defining
  107. else
  108. 824 top_level.defining?
  109. end
  110. end
  111. 2 def owner
  112. 653 if top_level?
  113. 413 @owner
  114. else
  115. 240 top_level.owner
  116. end
  117. end
  118. 2 def top_level?
  119. 3503 top_level == self
  120. end
  121. 2 def finalize
  122. 1224 freeze
  123. 4297 each { |_name, val| val.finalize if val.is_a? Set }
  124. end
  125. 2 def new_subset(name)
  126. 976 set = Set.new
  127. 976 set.name = name
  128. 976 set.top_level = top_level
  129. 976 if path == ''
  130. 817 set.path = name.to_s
  131. else
  132. 159 set.path = "#{path}.#{name}"
  133. end
  134. 976 set
  135. end
  136. 2 def live?
  137. 341 owner._live_parameter_requested?
  138. end
  139. 2 def live
  140. 32 owner._request_live_parameter
  141. 32 self
  142. end
  143. 2 def to_flat_hash(options = {})
  144. options = {
  145. 8 delimiter: '.'
  146. }.update(options)
  147. 8 flatten_params(self, options[:delimiter]).first
  148. end
  149. 2 private
  150. 2 def flatten_params(param_hash, delimiter, name = nil, results_hash = {})
  151. 56 param_hash.each do |k, v|
  152. 144 if v.is_a? Origen::Parameters::Set
  153. 48 name.nil? ? name = k.to_s : name << "#{delimiter}#{k}"
  154. 48 (results_hash, name) = flatten_params(v, delimiter, name, results_hash)
  155. else
  156. 96 if name.nil?
  157. 8 results_hash[k] = v
  158. else
  159. 88 results_hash["#{name}#{delimiter}#{k}"] = v
  160. 88 if k == param_hash.keys.last
  161. 48 name = name.include?(delimiter) ? name.split(delimiter)[0..-2].join(delimiter) : nil
  162. end
  163. end
  164. end
  165. end
  166. 56 [results_hash, name]
  167. end
  168. end
  169. end
  170. end

lib/origen/pins/function_proxy.rb

94.74% lines covered

19 relevant lines. 18 lines covered and 1 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 require 'delegate'
  4. 2 require 'origen/pins/pin'
  5. # Thin wrapper around pin objects to implement a defined function.
  6. #
  7. # The pin object stores all attributes associated with the function, this
  8. # wrapper simply keeps track of what function a given pin reference refers to
  9. 2 class FunctionProxy < ::Delegator
  10. 2 def initialize(id, pin)
  11. 22 @id = id
  12. 22 @pin = pin
  13. end
  14. 2 def __getobj__
  15. 28 @pin
  16. end
  17. # @api private
  18. #
  19. # To play nicely with == when a function proxy is wrapping a pin that is already
  20. # wrapped by an OrgFile interceptor
  21. 2 def __object__
  22. 2 @pin.__object__
  23. end
  24. # Intercept all calls to function-scoped attributes of the pin so
  25. # that we can inject the function that we want the attribute value for
  26. 2 Pin::FUNCTION_SCOPED_ATTRIBUTES.each do |attribute|
  27. 12 define_method attribute do |options = {}|
  28. 10 options[:function] = @id
  29. 10 @pin.send(attribute, options)
  30. end
  31. end
  32. 2 private
  33. # For debug
  34. 2 def _function
  35. @id
  36. end
  37. end
  38. end
  39. end

lib/origen/pins/ground_pin.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 class GroundPin < Pin
  4. end
  5. end
  6. end

lib/origen/pins/other_pin.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 class OtherPin < Pin
  4. end
  5. end
  6. end

lib/origen/pins/pin.rb

85.53% lines covered

615 relevant lines. 526 lines covered and 89 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 class Pin
  4. 2 include PinCommon
  5. 2 include OrgFile::Interceptable
  6. # Don't include the ! method in here, the cycle will be captured at the tester level and
  7. # it would cause a double cycle in the org file if also captured at the pin
  8. 2 ORG_FILE_INTERCEPTED_METHODS = [
  9. :suspend, :resume, :repeat_previous=,
  10. :drive_hi, :write_hi, :drive_very_hi, :drive_lo, :write_lo, :drive_mem, :expect_mem,
  11. :assert_hi, :expect_hi, :compare_hi, :read_hi, :assert_lo, :expect_lo, :compare_lo, :read_lo, :dont_care,
  12. :drive, :write, :assert, :compare, :expect, :read, :assert_midband, :compare_midband, :expect_midband, :read_midband,
  13. :toggle, :capture, :store
  14. ]
  15. # Any attributes listed here will be looked up for the current function defined
  16. # by the current mode and configuration context before falling back to a default
  17. 2 FUNCTION_SCOPED_ATTRIBUTES = [:name, :direction, :option, :group, :ip_block, :meta]
  18. # Any attributes listed here will be looked up for the current package context
  19. # before falling back to a default
  20. 2 PACKAGE_SCOPED_ATTRIBUTES = [:location, :dib_assignment, :dib_meta]
  21. # Pin Types
  22. 2 TYPES = [:analog, :digital]
  23. 2 attr_accessor :order
  24. # Inverts pin states for drive and compare, can be useful if a timing set change requires clocks to drive low for example when all pattern logic has been set up to drive them high.
  25. 2 attr_accessor :invert
  26. # Attribute used to generate vectors where the pin state is assigned the
  27. # repeat_previous opcode, used by Tester#repeat_previous
  28. 2 attr_accessor :repeat_previous
  29. 2 attr_reader :owner
  30. 2 attr_reader :size
  31. # Returns a hash containing the aliases associated with the given pin
  32. 2 attr_reader :aliases
  33. # Returns a hash containing the functions associated with the given pin
  34. 2 attr_reader :functions
  35. # Internal power supply pin is connected to
  36. 2 attr_accessor :supply
  37. 2 attr_accessor :supply_str
  38. # Boolean on whether pin is open drain
  39. 2 attr_accessor :open_drain
  40. # Boolean on whether pin has external pull-up
  41. 2 attr_accessor :ext_pullup
  42. # Boolean on whether pin has external pull-down
  43. 2 attr_accessor :ext_pulldown
  44. # Pin type, either :analog or :digital
  45. 2 attr_accessor :type
  46. # Pin RTL name
  47. 2 attr_accessor :rtl_name
  48. # Value to be forced on the pin, e.g. during simulation
  49. 2 attr_accessor :force
  50. 2 attr_accessor :description
  51. 2 attr_accessor :notes
  52. # Returns a hash containing any meta data associated with the current pin state
  53. #
  54. # my_pin.read!(1, meta: { position: 10 })
  55. # my_pin.state_meta # => { position: 10 }
  56. # my_pin.dont_care
  57. # my_pin.state_meta # => {}
  58. 2 attr_reader :state_meta
  59. # Should be instantiated through the HasPins macros
  60. 2 def initialize(id, owner, options = {}) # :nodoc:
  61. options = {
  62. 7440 reset: :dont_care,
  63. invert: false,
  64. direction: :io,
  65. open_drain: false,
  66. ext_pullup: false,
  67. ext_pulldown: false,
  68. rtl_name: nil
  69. }.merge(options)
  70. 7440 @aliases = {}
  71. 7440 @functions = {}
  72. 7440 @direction = sanitize_direction(options[:direction])
  73. 7440 @invert = options[:invert]
  74. 7440 @reset = options[:reset]
  75. 7440 @force = options[:force] & 1
  76. 7440 @id = id
  77. 7440 @name = options[:name]
  78. 7440 @rtl_name = options[:rtl_name]
  79. 7440 @suspend = false
  80. 7440 @order = options[:order]
  81. 7440 @supply = options[:supply]
  82. 7440 @open_drain = options[:open_drain]
  83. 7440 @ext_pullup = options[:ext_pullup]
  84. 7440 @ext_pulldown = options[:ext_pulldown]
  85. 7440 @type = options[:type]
  86. 7440 @dib_assignment = [] # Array to handle multi-site testing
  87. 7440 @size = 1
  88. 7440 @value = 0
  89. 7440 @clock = nil
  90. 7440 @meta = options[:meta] || {}
  91. 7440 @dib_meta = options[:dib_meta] || {}
  92. 7440 @state_meta = {}
  93. 7440 @_saved_state = []
  94. 7440 @_saved_value = []
  95. 7440 @_saved_suspend = []
  96. 7440 @_saved_invert = []
  97. 7440 @_saved_repeat_previous = []
  98. 7440 on_init(owner, options)
  99. # Assign the initial state from the method so that any inversion is picked up...
  100. 7440 send(@reset)
  101. end
  102. 2 def global_path_to
  103. "dut.pins(:#{id})"
  104. end
  105. 2 def org_file_intercepted_methods
  106. ORG_FILE_INTERCEPTED_METHODS
  107. end
  108. # Returns the drive cycle wave assigned to the pin based on the currently enabled timeset,
  109. # or nil if none is set.
  110. # Note that if a timeset is set then all pins will always return a wave as they will pick
  111. # up a default waveform if none is explicitly assigned to it.
  112. 2 def drive_wave(code = nil)
  113. 26 if t = dut.current_timeset
  114. # Cache this for performance since potentially this is something that could be called on
  115. # every cycle in some applications
  116. 25 @drive_waves ||= {}
  117. 25 @drive_waves[t.id] ||= {}
  118. 25 @drive_waves[t.id][code] ||= dut.current_timeset.send(:wave_for, myself, type: :drive, code: code)
  119. end
  120. end
  121. # Returns the compare cycle wave assigned to the pin based on the currently enabled timeset,
  122. # or nil if none is set
  123. # Note that if a timeset is set then all pins will always return a wave as they will pick
  124. # up a default waveform if none is explicitly assigned to it.
  125. 2 def compare_wave(code = nil)
  126. 18 if t = dut.current_timeset
  127. # Cache this for performance since potentially this is something that could be called on
  128. # every cycle in some applications
  129. 18 @compare_waves ||= {}
  130. 18 @compare_waves[t.id] ||= {}
  131. 18 @compare_waves[t.id][code] ||= dut.current_timeset.send(:wave_for, myself, type: :compare, code: code)
  132. end
  133. end
  134. 2 def rtl_name
  135. 6 if primary_group
  136. 4 (@rtl_name || "#{primary_group.id}#{primary_group_index}").to_s
  137. else
  138. 2 (@rtl_name || id).to_s
  139. end
  140. end
  141. # Causes the pin to continuously drive 1 for 2 seconds and then drive 0 for 2 seconds.
  142. #
  143. # This is not an API that is intended to be used within a pattern. Rather it is a debug aid when
  144. # setting up something like a bench test environment that uses Origen Link. For example you would
  145. # call this method on a pin from a console session, then confirm with a multimeter that the pin
  146. # is toggling on the relevant hardware.
  147. #
  148. # Call Pin#goodbye to stop it.
  149. #
  150. # @example Call from an origen console like this
  151. #
  152. # dut.pin(:tdi).hello
  153. 2 def hello
  154. drive_hi
  155. @@hello_pins ||= []
  156. @@hello_pins << myself unless @@hello_pins.include?(myself)
  157. @@hello_loop ||= Thread.new do
  158. loop do
  159. @@hello_pins.each(&:toggle)
  160. if $tester
  161. # Add a dummy timeset if one is not set yet, doesn't really matter what it is in this case
  162. # and better not to force the user to setup a debug workaround due to running outside of a pattern
  163. $tester.set_timeset('hello_world', 40) unless $tester.timeset
  164. $tester.cycle
  165. end
  166. sleep 2
  167. end
  168. end
  169. puts "Pin #{name} is toggling with a period of 2 seconds"
  170. end
  171. # See Pin#hello
  172. 2 def goodbye
  173. @@hello_pins.delete(myself)
  174. puts "Pin #{name} has stopped toggling"
  175. end
  176. # When sorting pins do it by ID
  177. 2 def <=>(other_pin)
  178. 3 @id <=> other_pin.id
  179. end
  180. 2 def name=(val)
  181. 34 @name = val
  182. end
  183. 2 def functions=(val)
  184. if val.is_a? Hash
  185. val.each do |name, _whatever|
  186. add_function name
  187. end
  188. else
  189. fail "Attempt to set the functions hash on pin #{@name}. Argument must be a Hash."
  190. end
  191. end
  192. # This generates getter methods that will lookup the given attribute within the
  193. # scope of the current package and falling back to a default if defined
  194. 2 PACKAGE_SCOPED_ATTRIBUTES.each do |attribute|
  195. 6 define_method attribute do |options = {}|
  196. 19 default = instance_variable_get("@#{attribute}")
  197. 19 package_id = options[:package] || current_package_id
  198. 19 package_id = package_id.to_sym if package_id
  199. 19 if packages[package_id]
  200. 18 packages[package_id][attribute] || default
  201. 1 elsif packages[:all]
  202. packages[:all][attribute] || default
  203. else
  204. 1 default
  205. end
  206. end
  207. end
  208. # This generates getter methods that will lookup the given attribute within the
  209. # scope of the function enabled by the current mode and configuration attributes
  210. # and falling back to a default if defined
  211. 2 FUNCTION_SCOPED_ATTRIBUTES.each do |attribute|
  212. 12 define_method attribute do |options = {}|
  213. 3968 default = instance_variable_get("@#{attribute}")
  214. 3968 if options[:function]
  215. 10 v = functions[:ids][options[:function]][attribute]
  216. 10 if v
  217. 10 if v.is_a?(Hash) && default.is_a?(Hash)
  218. return default.merge(v) # v will overwrite any default values
  219. else
  220. 10 return v
  221. end
  222. end
  223. # else fall back to context-based lookup
  224. end
  225. 3958 mode_id = options[:mode] || current_mode_id
  226. 3958 mode_id = mode_id.to_sym if mode_id
  227. 3958 mode = functions[mode_id] || functions[:all]
  228. 3958 if mode
  229. 42 config_id = options[:configuration] || options[:config] || current_configuration
  230. 42 config_id = config_id.to_sym if config_id
  231. 42 configuration = mode[config_id] || mode[:all]
  232. 42 if configuration
  233. 40 v = configuration[attribute]
  234. 40 if v
  235. 38 if v.is_a?(Hash) && default.is_a?(Hash)
  236. 6 return default.merge(v) # v will overwrite any default values
  237. else
  238. 32 return v
  239. end
  240. else
  241. 2 default
  242. end
  243. else
  244. 2 default
  245. end
  246. else
  247. 3916 default
  248. end
  249. end
  250. end
  251. 2 alias_method :function_scoped_name, :name
  252. # Returns the name of the pin, if a name has been specifically assigned by the application
  253. # (via name=) then this will be returned, otherwise the name of the current function if present
  254. # will be returned, and then as a last resort the ID of the pin
  255. 2 def name(options = {})
  256. # Return a specifically assigned name in preference to a function name
  257. 12957 (options.empty? ? @name : nil) || function_scoped_name(options) || @id
  258. end
  259. # Returns the value held by the pin as a string formatted to the current tester's pattern syntax
  260. #
  261. # @example
  262. #
  263. # pin.drive_hi
  264. # pin.to_vector # => "1"
  265. # pin.expect_lo
  266. # pin.to_vector # => "L"
  267. 2 def to_vector
  268. 125342 @vector_formatted_value ||= Origen.tester.format_pin_state(myself)
  269. end
  270. # @api private
  271. 2 def invalidate_vector_cache
  272. 43801 @vector_formatted_value = nil
  273. 57515 groups.each { |_name, group| group.invalidate_vector_cache }
  274. end
  275. # Set the pin value and state from a string formatted to the current tester's pattern syntax,
  276. # this is the opposite of the to_vector method
  277. #
  278. # @example
  279. #
  280. # pin.vector_formatted_value = "L"
  281. # pin.driving? # => false
  282. # pin.value # => 0
  283. # pin.vector_formatted_value = "1"
  284. # pin.driving? # => true
  285. # pin.value # => 1
  286. 2 def vector_formatted_value=(val)
  287. 26 unless @vector_formatted_value == val
  288. 22 Origen.tester.update_pin_from_formatted_state(myself, val)
  289. 22 @vector_formatted_value = val
  290. end
  291. end
  292. 2 def inspect
  293. "<#{myself.class}:#{object_id}>"
  294. end
  295. 2 def describe(options = {})
  296. 1 desc = ['********************']
  297. 1 desc << "Pin id: #{id}"
  298. 1 func_aliases = []
  299. 1 unless functions.empty?
  300. 1 desc << ''
  301. 1 desc << 'Functions'
  302. 1 desc << '---------'
  303. 1 functions.each do |mode, configurations|
  304. 3 unless mode == :ids
  305. 2 configurations.each do |configuration, attrs|
  306. 2 a = ":#{attrs[:name]}".ljust(30)
  307. 2 func_aliases << attrs[:name]
  308. 2 unless mode == :all
  309. 4 a += ":modes => [#{[mode].flatten.map { |id| ':' + id.to_s }.join(', ')}]"
  310. 2 prev = true
  311. end
  312. 2 unless configuration == :all
  313. a += ' ; ' if prev
  314. a += ":configurations => [#{[configuration].flatten.map { |id| ':' + id.to_s }.join(', ')}]"
  315. end
  316. 2 desc << a
  317. end
  318. end
  319. end
  320. end
  321. 1 unless aliases.empty?
  322. 1 desc << ''
  323. 1 desc << 'Aliases'
  324. 1 desc << '-------'
  325. 1 aliases.each do |name, context|
  326. 3 unless func_aliases.include?(name)
  327. 1 a = ":#{name}".ljust(30)
  328. 1 unless context[:packages].empty? || context[:packages] == [:all]
  329. a += ":packages => [#{context[:packages].map { |id| ':' + id.to_s }.join(', ')}]"
  330. prev = true
  331. end
  332. 1 unless context[:modes].empty? || context[:modes] == [:all]
  333. a += ' ; ' if prev
  334. a += ":modes => [#{context[:modes].map { |id| ':' + id.to_s }.join(', ')}]"
  335. prev = true
  336. end
  337. 1 unless context[:configurations].empty? || context[:configurations] == [:all]
  338. a += ' ; ' if prev
  339. a += ":configurations => [#{context[:configurations].map { |id| ':' + id.to_s }.join(', ')}]"
  340. end
  341. 1 desc << a
  342. end
  343. end
  344. end
  345. 1 unless Origen.top_level.modes.empty?
  346. 1 desc << ''
  347. 1 desc << 'Modes'
  348. 1 desc << '-------'
  349. 1 Origen.top_level.modes.each do |name|
  350. 4 unless option(mode: name).nil?
  351. a = ":#{name}".ljust(30) + ":mode => #{option(mode: name)}"
  352. desc << a
  353. end
  354. end
  355. end
  356. 1 unless groups.empty?
  357. desc << ''
  358. desc << 'Groups'
  359. desc << '------'
  360. desc << groups.map { |name, _group| ':' + name.to_s }.join(', ')
  361. end
  362. 1 desc << '********************'
  363. 1 if options[:return]
  364. 1 desc
  365. else
  366. puts desc.join("\n")
  367. end
  368. end
  369. # If the pin was defined initially as part of a group then this will return that group,
  370. # otherwise it will return nil
  371. 2 def group
  372. 10 @primary_group
  373. end
  374. 2 alias_method :primary_group, :group
  375. # If the pin is a member of a primary group, this returns its index number within that
  376. # group, otherwise returns nil
  377. 2 def group_index
  378. 4 @primary_group_index
  379. end
  380. 2 alias_method :primary_group_index, :group_index
  381. # Returns a hash containing the pin groups that the given pin is a member of
  382. 2 def groups
  383. # Origen.pin_bank.all_pin_groups.select do |name, group|
  384. 68023 @groups ||= Origen.pin_bank.pin_groups.select do |_name, group|
  385. 7889 group.include?(myself)
  386. end
  387. end
  388. 2 alias_method :pin_groups, :groups
  389. 2 def invalidate_group_cache
  390. 8652 @groups = nil
  391. end
  392. # Add a location identifier to the pin, this is a free format field which can be a
  393. # pin number or BGA co-ordinate for example.
  394. #
  395. # @example Adding a location by package
  396. # $dut.pin(:pin3).add_location "B3", :package => :p1
  397. # $dut.pin(:pin3).add_location "B2", :package => :p2
  398. 2 def add_location(str, options = {})
  399. 34 packages = resolve_packages(options)
  400. 34 if packages.empty?
  401. 1 @location = str
  402. 1 add_alias str.to_s.symbolize, package: :all, mode: :all, configuration: :all
  403. else
  404. 33 packages.each do |package_id|
  405. 33 package_id = package_id.respond_to?(:id) ? package_id.id : package_id
  406. 33 myself.packages[package_id] ||= {}
  407. 33 myself.packages[package_id][:location] = str
  408. 33 add_alias str.to_s.symbolize, package: package_id, mode: :all, configuration: :all
  409. end
  410. end
  411. end
  412. 2 alias_method :add_locn, :add_location
  413. # Add a Device Interface Board (e.g. probecard at wafer probe or loadboard at final package test)
  414. # assignment to the pin. Some refer to this as a channel but API name is meant to be generic.
  415. 2 def add_dib_assignment(str, options = {})
  416. options = {
  417. site: 0
  418. }.merge(options)
  419. packages = resolve_packages(options)
  420. if packages.empty?
  421. @dib_assignment[options[:site]] = str
  422. add_alias str.to_s.symbolize, package: :all, mode: :all, configuration: :all
  423. else
  424. packages.each do |package_id|
  425. package_id = package_id.respond_to?(:id) ? package_id.id : package_id
  426. myself.packages[package_id] ||= {}
  427. myself.packages[package_id][:dib_assignment] ||= []
  428. myself.packages[package_id][:dib_assignment][options[:site]] = str
  429. add_alias str.to_s.symbolize, package: package_id, mode: :all, configuration: :all
  430. end
  431. end
  432. end
  433. 2 alias_method :add_dib_info, :add_dib_assignment
  434. 2 alias_method :add_channel, :add_dib_assignment
  435. 2 def add_dib_meta(pkg, options)
  436. 2 unless Origen.top_level.packages.include? pkg
  437. Origen.log.error("Cannot add DIB metadata for package '#{pkg}', that package has not been added yet!")
  438. fail
  439. end
  440. 2 options.each do |attr, attr_value|
  441. 12 packages[pkg][:dib_meta] ||= {}
  442. 12 packages[pkg][:dib_meta][attr] = attr_value
  443. 12 add_alias attr_value.to_s.symbolize, package: pkg, mode: :all, configuration: :all
  444. end
  445. end
  446. # Returns the number of test sites enabled for the pin
  447. 2 def sites
  448. 1 dib_assignment.size
  449. end
  450. 2 def sanitize_direction(val)
  451. 7476 if val
  452. 7476 val = val.to_s.downcase.gsub(/\//, '')
  453. 7476 if val =~ /i.*o/
  454. 7429 :io
  455. 47 elsif val =~ /^i/
  456. 25 :input
  457. 22 elsif val =~ /^o/
  458. 22 :output
  459. else
  460. fail "Unknown pin direction: #{val}"
  461. end
  462. end
  463. end
  464. # Sets the default direction of the pin, :input, :output or :io (default). If a function specific
  465. # direction has been specified that will override this value.
  466. 2 def direction=(val)
  467. 21 @direction = sanitize_direction(val)
  468. end
  469. # Add a function to the pin.
  470. #
  471. # @example Adding a mode-specific function
  472. # pin.add_function :tdi, :direction => :input
  473. # pin.add_function :nvm_fail, :mode => :nvmbist, :direction => :output
  474. 2 def add_function(id, options = {})
  475. 22 id = id.to_sym
  476. 22 add_function_attributes(options.merge(name: id, id: id.to_sym))
  477. 22 f = FunctionProxy.new(id, myself)
  478. 22 add_alias id, packages: :all, obj: f
  479. end
  480. 2 def add_function_attributes(options = {})
  481. 25 id = options.delete(:id)
  482. 25 modes = resolve_modes(options)
  483. 25 configurations = resolve_configurations(options)
  484. 25 options[:direction] = sanitize_direction(options[:direction]) if options[:direction]
  485. 25 if modes.empty?
  486. 8 modes = [:all]
  487. end
  488. 25 if configurations.empty?
  489. 20 configurations = [:all]
  490. end
  491. # Supports newer attribute lookup by function ID
  492. 25 if id
  493. 22 functions[:ids] ||= {}
  494. 22 if functions[:ids][id]
  495. functions[:ids][id] = functions[:ids][id].merge!(options)
  496. else
  497. 22 functions[:ids][id] = options.dup
  498. end
  499. end
  500. # Supports older attribute lookup by mode context
  501. 25 modes.each do |mode|
  502. 25 configurations.each do |configuration|
  503. 25 functions[mode.to_sym] ||= {}
  504. 25 if functions[mode.to_sym][configuration.to_sym]
  505. 2 functions[mode.to_sym][configuration.to_sym] = functions[mode.to_sym][configuration.to_sym].merge!(options)
  506. else
  507. 23 functions[mode.to_sym][configuration.to_sym] = options
  508. end
  509. end
  510. end
  511. end
  512. # Add an alias to the given pin.
  513. #
  514. # If the options contain a package, mode or configuration reference then the alias
  515. # will only work under that context.
  516. 2 def add_alias(id, options = {})
  517. 1409 obj = options.delete(:obj) || myself
  518. 1409 if aliases[id]
  519. 1 aliases[id][:packages] += resolve_packages(options)
  520. 1 aliases[id][:modes] += resolve_modes(options)
  521. 1 aliases[id][:configurations] += resolve_configurations(options)
  522. 1 aliases[id][:packages].uniq!
  523. 1 aliases[id][:modes].uniq!
  524. 1 aliases[id][:configurations].uniq!
  525. else
  526. 1408 aliases[id] = {
  527. packages: resolve_packages(options),
  528. modes: resolve_modes(options),
  529. configurations: resolve_configurations(options)
  530. }
  531. 1408 Origen.pin_bank.register_alias(id, obj, options)
  532. end
  533. end
  534. # Returns true if the pin has the given alias within the given or current context
  535. 2 def has_alias?(id, options = {})
  536. 146 if aliases[id]
  537. 146 if options[:ignore_context]
  538. true
  539. else
  540. 146 packages = resolve_packages(options)
  541. 146 modes = resolve_modes(options)
  542. 146 configurations = resolve_configurations(options)
  543. begin
  544. 146 aliases[id][:packages].include?(:all) || aliases[id][:packages].empty? ||
  545. 20 packages.any? { |package| aliases[id][:packages].include?(package) }
  546. end && begin
  547. 132 aliases[id][:modes].include?(:all) || aliases[id][:modes].empty? ||
  548. 5 modes.any? { |mode| aliases[id][:modes].include?(mode) }
  549. end && begin
  550. 127 aliases[id][:configurations].include?(:all) || aliases[id][:configurations].empty? ||
  551. configurations.any? { |config| aliases[id][:configurations].include?(config) }
  552. end
  553. end
  554. else
  555. false
  556. end
  557. end
  558. # Returns true if the pin is an alias of the given pin name
  559. 2 def is_alias_of?(name)
  560. 9 if Origen.pin_bank.find(name)
  561. 7 Origen.pin_bank.find(name).id == Origen.pin_bank.find(myself).id
  562. else
  563. 2 false
  564. end
  565. end
  566. # Returns true if the pin belongs to a pin group.
  567. #
  568. # add_pins :jtag, size: 6
  569. # add_pin :done
  570. # add_pin_alias :fail, :jtag, pin: 4
  571. #
  572. # pin(:done).belongs_to_a_pin_group? # => false
  573. # pin(:fail).belongs_to_a_pin_group? # => true
  574. 2 def belongs_to_a_pin_group?
  575. 18788 !groups.empty?
  576. end
  577. 2 def value
  578. 14550 @value
  579. end
  580. 2 alias_method :data, :value
  581. 2 def suspend
  582. 6 invalidate_vector_cache
  583. 6 @suspend = true
  584. end
  585. 2 def suspended?
  586. 2812 @suspend
  587. end
  588. # Will resume compares on this pin
  589. 2 def resume
  590. 2818 invalidate_vector_cache
  591. 2818 @suspend = false
  592. end
  593. 2 def repeat_previous=(bool)
  594. 228 invalidate_vector_cache
  595. 228 @repeat_previous = bool
  596. end
  597. 2 def repeat_previous?
  598. 17115 @repeat_previous
  599. end
  600. 2 def set_state_with_options(state, options = {})
  601. 24237 @state_meta = options[:meta] || {}
  602. 24237 set_state(state)
  603. end
  604. 2 def set_state(state)
  605. 24237 invalidate_vector_cache
  606. 24237 @repeat_previous = false
  607. 24237 @state = state
  608. end
  609. 2 def set_value(val)
  610. 16379 orig = val
  611. 16379 invalidate_vector_cache
  612. 16379 if val.is_a?(String) || val.is_a?(Symbol)
  613. 4 val = val.to_s
  614. 4 if val =~ /^(b|h).+/
  615. val = Origen::Value.new(val)
  616. else
  617. 4 @vector_formatted_value = val
  618. 4 return
  619. end
  620. end
  621. 16375 if val.is_a?(Origen::Value)
  622. val = val[0]
  623. else
  624. # If val is a data bit extract the value of it
  625. 16375 val = val.respond_to?(:data) ? val.data : val
  626. # Assume driving/asserting a nil value means 0
  627. 16375 val = 0 unless val
  628. 16375 if !val.x_or_z? && val > 1
  629. fail "Attempt to set a value of #{val} on pin #{name}"
  630. end
  631. end
  632. 16375 @repeat_previous = false
  633. 16375 if val.x_or_z?
  634. dont_care
  635. else
  636. 16375 if inverted?
  637. @value = val == 0 ? 1 : 0
  638. else
  639. 16375 @value = val
  640. end
  641. end
  642. end
  643. 2 alias_method :data=, :set_value
  644. 2 def cycle # :nodoc:
  645. 92 Origen.tester.cycle
  646. end
  647. 2 def state
  648. 44189 @state
  649. end
  650. 2 def state=(value)
  651. invalidate_vector_cache
  652. @state_meta = {}
  653. @state = value
  654. end
  655. # Set the pin to drive a 1 on future cycles
  656. 2 def drive_hi(options = {})
  657. 799 set_state_with_options(:drive, options)
  658. 799 set_value(1)
  659. end
  660. 2 alias_method :write_hi, :drive_hi
  661. 2 def drive_hi!(options = {})
  662. drive_hi(options)
  663. cycle
  664. end
  665. 2 alias_method :write_hi!, :drive_hi!
  666. # Set the pin to drive a high voltage on future cycles (if the tester supports it).
  667. # For example on a J750 high-voltage channel the pin state would be set to "2"
  668. 2 def drive_very_hi(options = {})
  669. 41 set_state_with_options(:drive_very_hi, options)
  670. 41 set_value(1)
  671. end
  672. 2 def drive_very_hi!(options = {})
  673. drive_very_hi(options)
  674. cycle
  675. end
  676. # Set the pin to drive a 0 on future cycles
  677. 2 def drive_lo(options = {})
  678. 3600 set_state_with_options(:drive, options)
  679. 3600 set_value(0)
  680. end
  681. 2 alias_method :write_lo, :drive_lo
  682. 2 def drive_lo!(options = {})
  683. drive_lo(options)
  684. cycle
  685. end
  686. 2 alias_method :write_lo!, :drive_lo!
  687. 2 def drive_mem(options = {})
  688. 45 set_state_with_options(:drive_mem, options)
  689. end
  690. 2 def drive_mem!(options = {})
  691. 2 drive_mem(options)
  692. 2 cycle
  693. end
  694. 2 def expect_mem(options = {})
  695. 41 set_state_with_options(:expect_mem, options)
  696. end
  697. 2 def expect_mem!(options = {})
  698. 2 expect_mem(options)
  699. 2 cycle
  700. end
  701. # Set the pin to expect a 1 on future cycles
  702. 2 def assert_hi(options = {})
  703. 258 set_state_with_options(:compare, options)
  704. 258 set_value(1)
  705. end
  706. 2 alias_method :expect_hi, :assert_hi
  707. 2 alias_method :compare_hi, :assert_hi
  708. 2 alias_method :read_hi, :assert_hi
  709. 2 def assert_hi!(options = {})
  710. assert_hi(options)
  711. cycle
  712. end
  713. 2 alias_method :expect_hi!, :assert_hi!
  714. 2 alias_method :compare_hi!, :assert_hi!
  715. 2 alias_method :read_hi!, :assert_hi!
  716. # Set the pin to expect a 0 on future cycles
  717. 2 def assert_lo(options = {})
  718. 245 set_state_with_options(:compare, options)
  719. 245 set_value(0)
  720. # Planning to add the active load logic to the tester instead...
  721. # options = { :active => false #if active true means to take tester active load capability into account
  722. # }.merge(options)
  723. # unless state_to_be_inverted?
  724. # myself.state = ($tester.active_loads || !options[:active]) ? $tester.pin_state(:expect_lo) : $tester.pin_state(:dont_care)
  725. # else
  726. # myself.state = ($tester.active_loads || !options[:active]) ? $tester.pin_state(:expect_hi) : $tester.pin_state(:dont_care)
  727. # end
  728. end
  729. 2 alias_method :expect_lo, :assert_lo
  730. 2 alias_method :compare_lo, :assert_lo
  731. 2 alias_method :read_lo, :assert_lo
  732. 2 def assert_lo!(options = {})
  733. assert_lo(options)
  734. cycle
  735. end
  736. 2 alias_method :expect_lo!, :assert_lo!
  737. 2 alias_method :compare_lo!, :assert_lo!
  738. 2 alias_method :read_lo!, :assert_lo!
  739. # Set the pin to X on future cycles
  740. 2 def dont_care(options = {})
  741. 8230 set_state_with_options(:dont_care, options)
  742. end
  743. 2 def dont_care!(options = {})
  744. dont_care(options)
  745. cycle
  746. end
  747. # Pass in 0 or 1 to have the pin drive_lo or drive_hi respectively.
  748. # This is useful when programatically setting the pin state.
  749. # ==== Example
  750. # [0,1,1,0].each do |level|
  751. # $pin(:d_in).drive(level)
  752. # end
  753. 2 def drive(value, options = {})
  754. 10219 set_state_with_options(:drive, options)
  755. 10219 set_value(value)
  756. end
  757. 2 alias_method :write, :drive
  758. 2 def drive!(value, options = {})
  759. 77 drive(value, options)
  760. 77 cycle
  761. end
  762. 2 alias_method :write!, :drive!
  763. # Pass in 0 or 1 to have the pin expect_lo or expect_hi respectively.
  764. # This is useful when programatically setting the pin state.
  765. # ==== Example
  766. # [0,1,1,0].each do |level|
  767. # $pin(:d_in).assert(level)
  768. # end
  769. 2 def assert(value, options = {})
  770. 721 set_state_with_options(:compare, options)
  771. 721 set_value(value)
  772. end
  773. 2 alias_method :compare, :assert
  774. 2 alias_method :expect, :assert
  775. 2 alias_method :read, :assert
  776. 2 def assert!(*args)
  777. 11 assert(*args)
  778. 11 cycle
  779. end
  780. 2 alias_method :compare!, :assert!
  781. 2 alias_method :expect!, :assert!
  782. 2 alias_method :read!, :assert!
  783. 2 def assert_midband(options = {})
  784. set_state_with_options(:compare_midband, options)
  785. end
  786. 2 alias_method :compare_midband, :assert_midband
  787. 2 alias_method :expect_midband, :assert_midband
  788. 2 alias_method :read_midband, :assert_midband
  789. 2 def assert_midband!(options = {})
  790. assert_midband(options)
  791. cycle
  792. end
  793. 2 alias_method :compare_midband!, :assert_midband!
  794. 2 alias_method :expect_midband!, :assert_midband!
  795. 2 alias_method :read_midband!, :assert_midband!
  796. # Returns the state of invert
  797. 2 def inverted?
  798. 16379 @invert
  799. end
  800. # Returns true if the pin is currently in a compare state
  801. 2 def comparing?
  802. 4300 !@suspend &&
  803. state == :compare
  804. end
  805. # Returns true if the pin is currently in a compare mem state
  806. 2 def comparing_mem?
  807. 3146 !@suspend &&
  808. state == :expect_mem
  809. end
  810. # Returns true if the pin is currently in a compare state
  811. 2 def comparing_midband?
  812. 4171 !@suspend &&
  813. state == :compare_midband
  814. end
  815. # Returns true if the pin is currently in a drive state
  816. 2 def driving?
  817. 17016 !@suspend &&
  818. 17006 (state == :drive || state == :drive_very_hi)
  819. end
  820. # Returns true if the pin is currently in a drive mem state
  821. 2 def driving_mem?
  822. 3187 !@suspend &&
  823. state == :drive_mem
  824. end
  825. # Returns true if pin is in high voltage state
  826. 2 def high_voltage?
  827. 4575 !@suspend &&
  828. state == :drive_very_hi
  829. end
  830. 2 def toggle
  831. 516 unless state == :dont_care
  832. 496 set_value(value == 0 ? 1 : 0)
  833. end
  834. end
  835. 2 def toggle!
  836. toggle
  837. cycle
  838. end
  839. # Mark the (data) from the pin to be captured
  840. 2 def capture(options = {})
  841. 38 set_state_with_options(:capture, options)
  842. end
  843. 2 alias_method :store, :capture
  844. # Mark the (data) from the pin to be captured and trigger a cycle
  845. 2 def capture!(options = {})
  846. capture(options)
  847. cycle
  848. end
  849. 2 alias_method :store!, :capture!
  850. # Returns true if the (data) from the pin is marked to be captured
  851. 2 def to_be_captured?
  852. 3119 state == :capture
  853. end
  854. 2 alias_method :to_be_stored?, :to_be_captured?
  855. 2 alias_method :is_to_be_stored?, :to_be_captured?
  856. 2 alias_method :is_to_be_captured?, :to_be_captured?
  857. # Restores the state of the pin at the end of the given block
  858. # to the state it was in at the start of the block
  859. #
  860. # pin(:invoke).driving? # => true
  861. # pin(:invoke).restore_state do
  862. # pin(:invoke).dont_care
  863. # pin(:invoke).driving? # => false
  864. # end
  865. # pin(:invoke).driving? # => true
  866. 2 def restore_state
  867. 4 save
  868. 4 yield
  869. 4 restore
  870. end
  871. # Saves the current state of the pin, allowing it to be restored to the
  872. # current state by calling the restore method
  873. 2 def save
  874. 133 @_saved_state << @state
  875. 133 @_saved_value << @value
  876. 133 @_saved_suspend << @suspend
  877. 133 @_saved_invert << @invert
  878. 133 @_saved_repeat_previous << @repeat_previous
  879. end
  880. # Restores the state of the pin to the last time save was called
  881. 2 def restore
  882. 133 invalidate_vector_cache
  883. 133 @state = @_saved_state.pop
  884. 133 @value = @_saved_value.pop
  885. 133 @suspend = @_saved_suspend.pop
  886. 133 @invert = @_saved_invert.pop
  887. 133 @repeat_previous = @_saved_repeat_previous.pop
  888. end
  889. 2 def is_not_a_clock?
  890. 16 @clock.nil?
  891. end
  892. 2 def is_a_clock?
  893. 8 !(@clock.nil?)
  894. end
  895. 2 def is_a_running_clock?
  896. 200 @clock.running?
  897. end
  898. 2 def enable_clock(options = {})
  899. 9 @clock = PinClock.new(myself, options)
  900. end
  901. 2 def disable_clock(options = {})
  902. 3 @clock.stop_clock(options)
  903. 3 @clock = nil
  904. end
  905. 2 def update_clock
  906. 241 @clock.update_clock
  907. end
  908. 2 def start_clock(options = {})
  909. 16 enable_clock(options) if myself.is_not_a_clock?
  910. 16 @clock.start_clock(options)
  911. end
  912. 2 alias_method :resume_clock, :start_clock
  913. 2 def stop_clock(options = {})
  914. 14 @clock.stop_clock(options)
  915. end
  916. 2 alias_method :pause_clock, :stop_clock
  917. 2 def next_edge
  918. 296 @clock.next_edge
  919. end
  920. 2 def duty_cycles
  921. 21 @clock.cycles_per_duty
  922. end
  923. 2 def half_period
  924. 5 @clock.cycles_per_half_period
  925. end
  926. 2 def toggle_clock
  927. 188 fail "ERROR: Clock on #{@owner.name} not running." unless is_a_running_clock?
  928. 188 @clock.toggle
  929. end
  930. # Delete this pin (myself). Used bang in method name to keep same for pins and
  931. # pin collections. Pin collections already had a delete method which deletes
  932. # a pin from the collection. Needed delete! to indicate it is deleting the
  933. # actual pin or pin group calling the method.
  934. 2 def delete!
  935. 2 owner.delete_pin(myself)
  936. end
  937. 2 def type=(value)
  938. 2 if TYPES.include? value
  939. 2 @type = value
  940. else
  941. fail "Pin type '#{value}' must be set to one of the following: #{TYPES.join(', ')}"
  942. end
  943. end
  944. 2 def open_drain=(value)
  945. 1 if [true, false].include? value
  946. 1 @open_drain = value
  947. else
  948. fail "Pin open_drain attribute '#{value}' must be either true or false"
  949. end
  950. end
  951. 2 def ext_pullup=(value)
  952. 1 if [true, false].include? value
  953. 1 @ext_pullup = value
  954. else
  955. fail "Pin ext_pullup attribute '#{value}' must be either true or false"
  956. end
  957. end
  958. 2 def ext_pulldown=(value)
  959. 1 if [true, false].include? value
  960. 1 @ext_pulldown = value
  961. else
  962. fail "Pin ext_pulldown attribute '#{value}' must be either true or false"
  963. end
  964. end
  965. 2 def index?(context: nil)
  966. !!index(context: context).nil?
  967. end
  968. 2 def index(context: nil)
  969. 52 if context.is_a?(Symbol)
  970. # Context pin group provided
  971. 16 group = groups[context].instance_variable_get(:@store)
  972. 16 if group
  973. 13 group.index(self)
  974. end
  975. 36 elsif context.is_a?(Array)
  976. # Anonymous pin group given
  977. 84 context.map { |p| p.is_a?(Symbol) ? owner.pin(p) : p }.index(self)
  978. else
  979. # Try an index based off of the pin name.
  980. # Only works if the pin ends in a decimal. Otherwise, returns nil.
  981. 19 i = name.to_s.index(/\d+$/)
  982. 19 if i
  983. 16 name.to_s[i..-1].to_i
  984. end
  985. end
  986. end
  987. 2 def mask(context: nil)
  988. 16 index = context.is_a?(Integer) ? context : self.index(context: context)
  989. 16 if index.nil? && context.nil?
  990. # If the index is nil and no context was given, no implicit index could be resolved
  991. 1 fail("Could not discern pin :#{name}'s implicit index!")
  992. 15 elsif index.nil?
  993. # If the index is nil and some context was given, then the pin is not in the given context
  994. 1 fail("Pin :#{name} is not a member of the given context!")
  995. end
  996. 14 2**index
  997. end
  998. 2 alias_method :set_mask, :mask
  999. 2 alias_method :smask, :mask
  1000. 2 def clear_mask(context: nil, size: nil)
  1001. 23 index = context.is_a?(Integer) ? context : self.index(context: context)
  1002. 23 if index.nil? && context.nil?
  1003. # If the index is nil and no context was given, no implicit index could be resolved
  1004. 1 fail("Could not discern pin :#{name}'s implicit index!")
  1005. 22 elsif index.nil?
  1006. # If the index is nil and some context was given, then the pin is not in the given context
  1007. 1 fail("Pin :#{name} is not a member of the given context!")
  1008. end
  1009. 21 if size && context && !context.is_a?(Integer)
  1010. # A context was given, that was not just an Integer, and size was given
  1011. # Raise an exception as these two conflict.
  1012. 1 fail('Both a sized context (e.g. pin group) and a :size option cannot be used simultaneously!')
  1013. 20 elsif size
  1014. # A size option was given. Use that.
  1015. 6 ((2**size) - 1) ^ (1 << index)
  1016. 14 elsif context.is_a?(Symbol)
  1017. 4 ((2**groups[context].instance_variable_get(:@store).size) - 1) ^ (1 << index)
  1018. 10 elsif context.respond_to?(:size) && !context.is_a?(Integer)
  1019. # PinCollection or Array
  1020. 4 ((2**context.size) - 1) ^ (1 << index)
  1021. else
  1022. # No size option was given. Use the implicit index instead.
  1023. 6 (2**index) - 1
  1024. end
  1025. end
  1026. 2 alias_method :clr_mask, :clear_mask
  1027. 2 alias_method :cmask, :clear_mask
  1028. 2 def named?(n)
  1029. 5 if n.is_a?(Regexp)
  1030. 6 [name.to_s, *aliases.keys].any? { |na| na =~ n }
  1031. else
  1032. 3 [name.to_s, *aliases.keys.map(&:to_s)].include?(n.to_s)
  1033. end
  1034. end
  1035. 2 def method_missing(m, *args, &block)
  1036. 1 if meta.include? m
  1037. 1 meta[m]
  1038. else
  1039. super
  1040. end
  1041. end
  1042. 2 def respond_to_missing?(m, include_private = false)
  1043. 143 meta[m] || super
  1044. end
  1045. 2 private
  1046. 2 def primary_group=(group)
  1047. 4142 @primary_group = group
  1048. end
  1049. 2 def primary_group_index=(number)
  1050. 4142 @primary_group_index = number
  1051. end
  1052. end
  1053. end
  1054. end

lib/origen/pins/pin_bank.rb

93.75% lines covered

224 relevant lines. 210 lines covered and 14 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. # Stores all pins, pin aliases and pin groups for the current target.
  4. # A central store is used to allow either top-level or sub-block objects to
  5. # add pins to the current context available to the testers.
  6. #
  7. # The global Origen pin bank (an instance of this class) is returned from Origen.pin_bank.
  8. 2 class PinBank
  9. 2 include Origen::CoreCallbacks
  10. # There is one pin bank per Origen thread, this clears the bank every time the target is changed
  11. #
  12. # @api private
  13. 2 def before_load_target
  14. 590 empty!
  15. end
  16. # Add the given pin to the bank
  17. #
  18. # @return [Origen::Pins::Pin] the supplied pin object
  19. 2 def add_pin(pin, _owner, _options = {})
  20. 7440 if pin.is_a?(PowerPin)
  21. 85 bank = all_power_pins
  22. 7355 elsif pin.is_a?(GroundPin)
  23. 88 bank = all_ground_pins
  24. 7267 elsif pin.is_a?(VirtualPin)
  25. 78 bank = all_virtual_pins
  26. 7189 elsif pin.is_a?(OtherPin)
  27. 54 bank = all_other_pins
  28. else
  29. 7135 bank = all_pins
  30. end
  31. 7440 if bank[pin.id]
  32. fail "A pin with id #{pin.id} already exists!"
  33. end
  34. 7440 all_ids << pin.id
  35. 7440 bank[pin.id] = pin
  36. # If ends in a number
  37. # if !options[:dont_create_group] && pin.id.to_s =~ /(.*?)(\d+)$/
  38. # # Create a new group if one with the given name doesn't already exist
  39. # unless group = all_pin_groups[$1.to_sym]
  40. # group = PinCollection.new(owner, options)
  41. # group.id = $1.to_sym
  42. # all_pin_groups[$1.to_sym] = group
  43. # end
  44. # group.add_pin(pin)
  45. # end
  46. 7440 pin
  47. end
  48. 2 def add_pin_group(group, owner, options = {})
  49. 459 unless options[:pins_exist]
  50. 459 group.each do |pin|
  51. 4142 add_pin(pin, owner, options.merge(dont_create_group: true))
  52. end
  53. end
  54. 459 store_pin_group(group, options)
  55. 459 group
  56. end
  57. # Returns a hash containing all pins available in the current context stored by their primary ID
  58. 2 def pins(options = {})
  59. 1495 all_pins.select do |_id, pin|
  60. 45875 pin.enabled?(options)
  61. end
  62. end
  63. 2 def power_pins(options = {})
  64. 19 all_power_pins.select do |_id, pin|
  65. 50 pin.enabled?(options)
  66. end
  67. end
  68. 2 def ground_pins(options = {})
  69. 20 all_ground_pins.select do |_id, pin|
  70. 58 pin.enabled?(options)
  71. end
  72. end
  73. 2 def other_pins(options = {})
  74. 3 all_other_pins.select do |_id, pin|
  75. 10 pin.enabled?(options)
  76. end
  77. end
  78. 2 def virtual_pins(options = {})
  79. 16 all_virtual_pins.select do |_id, pin|
  80. 36 pin.enabled?(options)
  81. end
  82. end
  83. # Returns a hash containing all pin_groups available in the current context stored by their primary ID
  84. 2 def pin_groups(options = {})
  85. 12057 current_pin_group_store(all_pin_groups, options).select do |_id, group|
  86. 19616 group.enabled?(options)
  87. end
  88. end
  89. # Returns a hash containing all power_pin_groups available in the current context stored by their primary ID
  90. 2 def power_pin_groups(options = {})
  91. 8 current_pin_group_store(all_power_pin_groups, options).select do |_id, group|
  92. 7 group.enabled?(options)
  93. end
  94. end
  95. # Returns a hash containing all ground_pin_groups available in the current context stored by their primary ID
  96. 2 def ground_pin_groups(options = {})
  97. 8 current_pin_group_store(all_ground_pin_groups, options).select do |_id, group|
  98. 7 group.enabled?(options)
  99. end
  100. end
  101. # Returns a hash containing all ground_pin_groups available in the current context stored by their primary ID
  102. 2 def other_pin_groups(options = {})
  103. 4 current_pin_group_store(all_other_pin_groups, options).select do |_id, group|
  104. 3 group.enabled?(options)
  105. end
  106. end
  107. # Returns a hash containing all virtual_pin_groups available in the current context stored by their primary ID
  108. 2 def virtual_pin_groups(options = {})
  109. 4 current_pin_group_store(all_virtual_pin_groups, options).select do |_id, group|
  110. 3 group.enabled?(options)
  111. end
  112. end
  113. # Returns a hash containing all pins stored by their primary ID
  114. 2 def all_pins
  115. 20482 @all_pins ||= {}
  116. end
  117. # Returns a hash containing all pin groups stored by context
  118. 2 def all_pin_groups
  119. 19565 @all_pin_groups ||= {}
  120. end
  121. # Returns a hash containing all power pins stored by their primary ID
  122. 2 def all_power_pins
  123. 155 @all_power_pins ||= {}
  124. end
  125. # Returns a hash containing all ground pins stored by their primary ID
  126. 2 def all_ground_pins
  127. 159 @all_ground_pins ||= {}
  128. end
  129. # Returns a hash containing all other pins stored by their primary ID
  130. 2 def all_other_pins
  131. 74 @all_other_pins ||= {}
  132. end
  133. # Returns a hash containing all virtual pins stored by their primary ID
  134. 2 def all_virtual_pins
  135. 111 @all_virtual_pins ||= {}
  136. end
  137. # Returns a hash containing all power pin groups stored by context
  138. 2 def all_power_pin_groups
  139. 39 @all_power_pin_groups ||= {}
  140. end
  141. # Returns a hash containing all ground pin groups stored by context
  142. 2 def all_ground_pin_groups
  143. 40 @all_ground_pin_groups ||= {}
  144. end
  145. # Returns a hash containing all other pin groups stored by context
  146. 2 def all_other_pin_groups
  147. 12 @all_other_pin_groups ||= {}
  148. end
  149. # Returns a hash containing all virtual pin groups stored by context
  150. 2 def all_virtual_pin_groups
  151. 10 @all_virtual_pin_groups ||= {}
  152. end
  153. 2 def find(id, options = {})
  154. 11985 id = id.to_sym
  155. 11985 if options[:power_pin]
  156. 51 pin = all_power_pins[id] || find_pin_group(id, options)
  157. 11934 elsif options[:ground_pin]
  158. 51 pin = all_ground_pins[id] || find_pin_group(id, options)
  159. 11883 elsif options[:virtual_pin]
  160. 17 pin = all_virtual_pins[id] || find_pin_group(id, options)
  161. 11866 elsif options[:other_pin]
  162. 17 pin = all_other_pins[id] || find_pin_group(id, options)
  163. else
  164. 11849 pin = all_pins[id] || find_by_alias(id, options) || find_pin_group(id, options)
  165. end
  166. 11985 if pin
  167. 11963 if options[:ignore_context] || pin.enabled?(options)
  168. 11953 pin
  169. end
  170. end
  171. end
  172. 2 def find_pin_group(id, options = {})
  173. options = {
  174. 6460 include_all: true
  175. }.merge(options)
  176. 6460 if options[:power_pin]
  177. 17 base = all_power_pin_groups
  178. 6443 elsif options[:ground_pin]
  179. 18 base = all_ground_pin_groups
  180. 6425 elsif options[:other_pin]
  181. 6 base = all_other_pin_groups
  182. 6419 elsif options[:virtual_pin]
  183. 4 base = all_virtual_pin_groups
  184. else
  185. 6415 base = all_pin_groups
  186. end
  187. 6460 pin_group_stores_in_context(base, options) do |store|
  188. 6474 return store[id] if store[id]
  189. end
  190. nil
  191. end
  192. # This will be called by the pins whenever a new alias is added to them
  193. 2 def register_alias(id, pin, _options = {})
  194. 1408 known_aliases[id] ||= []
  195. 1408 known_aliases[id] << pin
  196. end
  197. # Find an existing pin group with the given ID if it exists and return it, otherwise create one
  198. 2 def find_or_create_pin_group(id, owner, options = {})
  199. 668 group = find_pin_group(id, options)
  200. 668 unless group
  201. 664 group = PinCollection.new(owner, options)
  202. 664 group.id = id
  203. 664 store_pin_group(group, options)
  204. end
  205. 668 group
  206. end
  207. # Delete a specific pin
  208. 2 def delete_pin(pin)
  209. 3 if pin.is_a?(PowerPin)
  210. bank = all_power_pins
  211. 3 elsif pin.is_a?(GroundPin)
  212. bank = all_ground_pins
  213. 3 elsif pin.is_a?(OtherPin)
  214. bank = all_other_pins
  215. 3 elsif pin.is_a?(VirtualPin)
  216. bank = all_virtual_pins
  217. else
  218. 3 bank = all_pins
  219. end
  220. # First delete the pin from any of the pin groups it resides
  221. 3 Origen.pin_bank.pin_groups.each do |_name, grp|
  222. 1 next unless grp.store.include?(pin)
  223. 1 grp.delete(pin)
  224. end
  225. # Now delete the pin from the pin bank
  226. 3 if bank[pin.id]
  227. 3 bank.delete(pin.id)
  228. # Delete all known aliases as well
  229. 3 known_aliases.delete(pin.name)
  230. else
  231. if pin.id == pin.name
  232. fail "A pin with id #{pin.id} does not exist!"
  233. else
  234. fail "A pin with id #{pin.id} and name #{pin.name} does not exist!"
  235. end
  236. end
  237. end
  238. # Delete a specific pin group
  239. 2 def delete_pingroup(group)
  240. 2 found_group = false
  241. 2 if group.power_pins?
  242. base = all_power_pin_groups
  243. 2 elsif group.ground_pins?
  244. base = all_ground_pin_groups
  245. 2 elsif group.other_pins?
  246. base = all_other_pin_groups
  247. 2 elsif group.virtual_pins?
  248. base = all_virtual_pin_groups
  249. else
  250. 2 base = all_pin_groups
  251. end
  252. 2 pin_group_stores_in_context(base) do |store|
  253. 2 if store.include?(group.id)
  254. 2 store.delete(group.id)
  255. 2 found_group = true
  256. end
  257. end
  258. 2 fail "A pin group with id #{group.id} does not exist!" unless found_group == true
  259. end
  260. 2 private
  261. 2 def current_pin_group_store(base, options)
  262. 12081 pin_group_stores_in_context(base, options)
  263. end
  264. 2 def pin_group_stores_in_context(base, options = {})
  265. # Pin group availability is now only scoped by package
  266. 19666 options[:mode] = :all
  267. 19666 options[:configuration] = :all
  268. 19666 resolve_packages(options).each do |package|
  269. 19680 base[package] ||= {}
  270. 19680 resolve_modes(options).each do |mode|
  271. 19680 base[package][mode] ||= {}
  272. 19680 resolve_configurations(options).each do |config|
  273. 19680 base[package][mode][config] ||= {}
  274. 19680 if block_given?
  275. 7599 yield base[package][mode][config]
  276. else
  277. 12081 return base[package][mode][config]
  278. end
  279. end
  280. end
  281. end
  282. end
  283. 2 def store_pin_group(group, options)
  284. 1123 if group.power_pins?
  285. 14 base = all_power_pin_groups
  286. 1109 elsif group.ground_pins?
  287. 14 base = all_ground_pin_groups
  288. 1095 elsif group.other_pins?
  289. 2 base = all_other_pin_groups
  290. 1093 elsif group.virtual_pins?
  291. 2 base = all_virtual_pin_groups
  292. else
  293. 1091 base = all_pin_groups
  294. end
  295. 1123 pin_group_stores_in_context(base, options) do |store|
  296. 1123 store[group.id] = group
  297. end
  298. end
  299. # Returns an array containing the package ids resolved from the given options or
  300. # the current top-level context
  301. 2 def resolve_packages(options = {})
  302. 19666 p = [options.delete(:package) || options.delete(:packages) || current_package_id].flatten.compact
  303. 19666 if options[:include_all] || p.empty?
  304. 19636 p << :all
  305. end
  306. 19666 p.uniq
  307. end
  308. # Returns an array containing the mode ids resolved from the given options or
  309. # the current top-level context
  310. 2 def resolve_modes(options = {})
  311. 19680 m = [options.delete(:mode) || options.delete(:modes) || current_mode_id].flatten.compact
  312. 19680 if options[:include_all] || m.empty?
  313. 6474 m << :all
  314. end
  315. 19680 m.uniq
  316. end
  317. # Returns an array containing the configuration ids resolved from the given options or
  318. # the current top-level context
  319. 2 def resolve_configurations(options = {})
  320. 19680 c = [options.delete(:configuration) || options.delete(:configurations) || current_configuration].flatten.compact
  321. 19680 if options[:include_all] || c.empty?
  322. 6474 c << :all
  323. end
  324. 19680 c.uniq
  325. end
  326. # Returns the current configuration context for this pin/pin group, if a configuration has been
  327. # explicitly set on this pin that will be returned, otherwise the current chip-level configuration
  328. # context will be returned (nil if none is set)
  329. 2 def current_configuration
  330. 14 if Origen.top_level
  331. 14 Origen.top_level.current_configuration
  332. end
  333. end
  334. # Returns the current top-level package ID, nil if none is set.
  335. 2 def current_package_id
  336. 19641 if Origen.top_level && Origen.top_level.current_package
  337. 33 Origen.top_level.current_package.id
  338. end
  339. end
  340. # Returns the current top-level mode ID, nil if none is set.
  341. 2 def current_mode_id
  342. 14 if Origen.top_level && Origen.top_level.current_mode
  343. Origen.top_level.current_mode.id
  344. end
  345. end
  346. 2 def find_by_alias(id, options = {})
  347. 5903 if known_aliases[id]
  348. 140 pins = known_aliases[id].select do |pin|
  349. 145 pin.has_alias?(id, options)
  350. end
  351. 140 if pins.size > 1
  352. fail "Mutliple pins with the alias #{id} have been found in the current scope!"
  353. end
  354. 140 pins.first
  355. end
  356. end
  357. # Delete all pins, groups and aliases from the bank
  358. 2 def empty!
  359. 591 @all_ids = nil
  360. 591 @known_aliases = nil
  361. 591 @all_pins = nil
  362. 591 @all_power_pins = nil
  363. 591 @all_ground_pins = nil
  364. 591 @all_other_pins = nil
  365. 591 @all_virtual_pins = nil
  366. 591 @all_pin_groups = nil
  367. 591 @all_power_pin_groups = nil
  368. 591 @all_ground_pin_groups = nil
  369. 591 @all_other_pin_groups = nil
  370. 591 @all_virtual_pin_groups = nil
  371. end
  372. 2 def known_aliases
  373. 8862 @known_aliases ||= {}
  374. end
  375. 2 def all_ids
  376. 7440 @all_ids ||= []
  377. end
  378. end
  379. end
  380. end

lib/origen/pins/pin_clock.rb

95.12% lines covered

82 relevant lines. 78 lines covered and 4 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 class PinClock
  4. 2 attr_reader :cycles_per_duty, :last_edge, :next_edge
  5. 2 def initialize(owner, options = {})
  6. 9 @owner = owner
  7. 9 @running = false
  8. 9 @clock_period_in_ns = 0
  9. 9 @tester_period_in_ns = 0
  10. 9 update_clock_period(options)
  11. 9 update_tester_period_local
  12. 9 update_clock_parameters
  13. end
  14. 2 def start_clock(options = {})
  15. # Throw error if this pin is already a running clock
  16. 16 if running?
  17. fail "PIN CLOCK ERROR: Clock on #{@owner.name} already running."
  18. end
  19. 16 clock_updated = update_clock_period(options)
  20. 16 tester_updated = update_tester_period_local
  21. 16 if clock_updated || tester_updated
  22. 4 update_clock_parameters
  23. end
  24. 16 cc "[PinClock] Start #{@owner.name}.clock at #{Origen.tester.cycle_count}: period=#{@clock_period_in_ns}ns, cycles=#{cycles_per_period}, duty=#{duty_str}"
  25. 16 update_edges
  26. 16 Origen.tester.push_running_clock(@owner) unless running?
  27. 16 @running = true
  28. end
  29. 2 def stop_clock(options = {})
  30. 17 cc "[PinClock] Stop #{@owner.name}.clock: stop_cycle=#{Origen.tester.cycle_count}" if running?
  31. 17 Origen.tester.pop_running_clock(@owner) if running?
  32. 17 @running = false
  33. end
  34. 2 def restart_clock
  35. stop_clock
  36. update_clock
  37. start_clock
  38. end
  39. 2 def update_clock
  40. 241 if update_tester_period_local
  41. 5 update_clock_parameters
  42. 5 cc "[PinClock] Update #{@owner.name}.clock at #{Origen.tester.cycle_count}: period=#{@clock_period_in_ns}ns, cycles=#{cycles_per_period}, duty=#{duty_str}"
  43. 5 update_edges
  44. end
  45. end
  46. 2 def running?
  47. 266 @running
  48. end
  49. 2 def toggle
  50. 188 @owner.toggle
  51. 188 update_edges
  52. end
  53. # The only caller to this should be legacy support so just force 50% duty cycle
  54. 2 def cycles_per_half_period
  55. 5 @cycles_per_duty.min
  56. end
  57. 2 private
  58. 2 def update_clock_parameters
  59. 18 @cycles_per_duty = [(cycles_per_period / 2.0).floor, (cycles_per_period / 2.0).ceil]
  60. end
  61. 2 def cycles_per_period
  62. 57 (@clock_period_in_ns / @tester_period_in_ns).to_int
  63. end
  64. 2 def update_edges
  65. 209 @last_edge = Origen.tester.cycle_count
  66. 209 @next_edge = Origen.tester.cycle_count + @cycles_per_duty[0]
  67. 209 @cycles_per_duty.reverse!
  68. end
  69. 2 def update_tester_period_local
  70. 266 if Origen.tester.current_period_in_ns == @tester_period_in_ns
  71. 250 return false
  72. else
  73. 16 @tester_period_in_ns = Origen.tester.current_period_in_ns
  74. 16 return true
  75. end
  76. end
  77. 2 def update_clock_period(options = {})
  78. 25 new = get_clock_period(options)
  79. 25 if new == @clock_period_in_ns
  80. 14 false
  81. else
  82. 11 @clock_period_in_ns = new
  83. 11 true
  84. end
  85. end
  86. 2 def get_clock_period(options = {})
  87. 25 return @clock_period_in_ns if options.empty?
  88. 11 p = []
  89. # Passed in as time
  90. 11 p << (options[:period_in_s] * 1_000_000_000) if options[:period_in_s]
  91. 11 p << (options[:period_in_ms] * 1_000_000) if options[:period_in_ms]
  92. 11 p << (options[:period_in_us] * 1_000) if options[:period_in_us]
  93. 11 p << (options[:period_in_ns] * 1) if options[:period_in_ns]
  94. # Passed in as frequency (or freq.)
  95. 11 p << ((1.0 / options[:frequency_in_hz]) * 1_000_000_000) if options[:frequency_in_hz]
  96. 11 p << ((1.0 / options[:freq_in_hz]) * 1_000_000_000) if options[:freq_in_hz]
  97. 11 p << ((1.0 / options[:frequency_in_khz]) * 1_000_000) if options[:frequency_in_khz]
  98. 11 p << ((1.0 / options[:freq_in_khz]) * 1_000_000) if options[:freq_in_khz]
  99. 11 p << ((1.0 / options[:frequency_in_mhz]) * 1_000) if options[:frequency_in_mhz]
  100. 11 p << ((1.0 / options[:freq_in_mhz]) * 1_000) if options[:freq_in_mhz]
  101. # Passed in as cycles (not advised)
  102. 11 p << (options[:cycles] * Origen.tester.period_in_ns) if options[:cycles]
  103. 11 return @clock_period_in_ns if p.empty?
  104. 11 fail "[Pin Clock] ERROR: Multiple unit declarations for #{@owner.name}.clock" if p.size > 1
  105. 11 p[0].to_int
  106. end
  107. 2 def duty_str
  108. 21 "#{@cycles_per_duty[0]}/#{@cycles_per_duty[1]}"
  109. end
  110. end
  111. end
  112. end

lib/origen/pins/pin_collection.rb

90.16% lines covered

305 relevant lines. 275 lines covered and 30 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. # A class that is used to wrap collections of one or more pins. Anytime a group
  4. # of pins is fetched or returned by the Pin API it will be wrapped in a PinCollection.
  5. 2 class PinCollection
  6. 2 include PinCommon
  7. 2 include Enumerable
  8. 2 include OrgFile::Interceptable
  9. 2 ORG_FILE_INTERCEPTED_METHODS = [
  10. :drive, :write, :drive_hi, :write_hi, :drive_lo, :write_lo, :drive_very_hi, :drive_mem, :expect_mem, :toggle,
  11. :repeat_previous=, :capture, :assert, :read, :compare, :expect,
  12. :assert_hi, :expect_hi, :compare_hi, :read_hi, :assert_lo, :expect_lo, :compare_lo, :read_lo,
  13. :dont_care
  14. ]
  15. 2 attr_accessor :endian
  16. 2 attr_accessor :description
  17. 2 attr_accessor :group
  18. 2 attr_accessor :group_str
  19. 2 attr_accessor :color
  20. 2 def initialize(owner, *pins)
  21. 1334 options = pins.last.is_a?(Hash) ? pins.pop : {}
  22. options = {
  23. 1334 endian: :big
  24. }.merge(options)
  25. 1334 @power_pins = options.delete(:power_pin) || options.delete(:power_pins)
  26. 1334 @ground_pins = options.delete(:ground_pin) || options.delete(:ground_pins)
  27. 1334 @virtual_pins = options.delete(:virtual_pin) || options.delete(:virtual_pins)
  28. 1334 @other_pins = options.delete(:other_pin) || options.delete(:other_pins)
  29. 1334 @endian = options[:endian]
  30. 1334 @rtl_name = options[:rtl_name]
  31. 1334 @description = options[:description] || options[:desc]
  32. 1334 @options = options
  33. 1334 @store = []
  34. 1334 pins.each_with_index do |pin, i|
  35. 38 @store[i] = pin
  36. end
  37. 1334 on_init(owner, options)
  38. end
  39. 2 def rtl_name
  40. 2 (@rtl_name || id).to_s
  41. end
  42. 2 def global_path_to
  43. "dut.pins(:#{id})"
  44. end
  45. 2 def org_file_intercepted_methods
  46. ORG_FILE_INTERCEPTED_METHODS
  47. end
  48. # Returns the value held by the pin group as a string formatted to the current tester's pattern syntax
  49. #
  50. # @example
  51. #
  52. # pin_group.drive_hi
  53. # pin_group.to_vector # => "11111111"
  54. # pin_group.expect_lo
  55. # pin_group.to_vector # => "LLLLLLLL"
  56. 2 def to_vector
  57. 42743 return @vector_formatted_value if @vector_formatted_value
  58. 518 vals = map(&:to_vector)
  59. 518 vals.reverse! if endian == :little
  60. 518 @vector_formatted_value = vals.join('')
  61. end
  62. # @api private
  63. 2 def invalidate_vector_cache
  64. 13716 @vector_formatted_value = nil
  65. end
  66. # Set the values and states of the pin group's pins from a string formatted to the current tester's pattern syntax,
  67. # this is the opposite of the to_vector method
  68. #
  69. # @example
  70. #
  71. # pin_group.vector_formatted_value = "LLLLLLLL"
  72. # pin_group[0].driving? # => false
  73. # pin_group[0].value # => 0
  74. # pin_group.vector_formatted_value = "HHHH1111"
  75. # pin_group[0].driving? # => true
  76. # pin_group[0].value # => 1
  77. # pin_group[7].driving? # => false
  78. # pin_group[7].value # => 1
  79. 2 def vector_formatted_value=(val)
  80. 6 unless @vector_formatted_value == val
  81. 6 unless val.size == size
  82. fail 'When setting vector_formatted_value on a pin group you must supply values for all pins!'
  83. end
  84. 6 val.split(//).reverse.each_with_index do |val, i|
  85. 26 myself[i].vector_formatted_value = val
  86. end
  87. 6 @vector_formatted_value = val
  88. end
  89. end
  90. # Returns true if the pin collection contains power pins rather than regular pins
  91. 2 def power_pins?
  92. 4903 @power_pins
  93. end
  94. # Returns true if the pin collection contains ground pins rather than regular pins
  95. 2 def ground_pins?
  96. 4860 @ground_pins
  97. end
  98. # Returns true if the pin collection contains virtual pins rather than regular pins
  99. 2 def virtual_pins?
  100. 4798 @virtual_pins
  101. end
  102. # Returns true if the pin collection contains other pins rather than regular pins
  103. 2 def other_pins?
  104. 4805 @other_pins
  105. end
  106. 2 def id
  107. 7806 @id
  108. end
  109. # Explicitly set the name of a pin group/collection
  110. 2 def name=(val)
  111. @name = val
  112. end
  113. 2 def name
  114. 2509 @name || id
  115. end
  116. # Overrides the regular Ruby array each to be endian aware. If the pin collection/group is
  117. # defined as big endian then this will yield the least significant pin first, otherwise for
  118. # little endian the most significant pin will come out first.
  119. 2 def each
  120. 13103 size.times do |i|
  121. 123813 if endian == :big
  122. 94795 yield @store[size - i - 1]
  123. else
  124. 29018 yield @store[i]
  125. end
  126. end
  127. end
  128. 2 def size
  129. 110833 @store.size
  130. end
  131. 2 def [](*indexes)
  132. 8326 if indexes.size > 1 || indexes.first.is_a?(Range)
  133. 199 p = PinCollection.new(owner, @options)
  134. 199 expand_and_order(indexes).each do |index|
  135. 792 p << @store[index]
  136. end
  137. 199 p
  138. else
  139. 8127 @store[indexes.first]
  140. end
  141. end
  142. 2 def sort!(&block)
  143. 2 @store = sort(&block)
  144. 2 myself
  145. end
  146. 2 def sort_by!
  147. @store = sort_by
  148. myself
  149. end
  150. 2 def []=(index, pin)
  151. 4142 @store[index] = pin
  152. end
  153. # Describe the pin group contents. Default is to display pin.id but passing in
  154. # :name will display pin.name
  155. 2 def describe(display = :id)
  156. desc = ['********************']
  157. desc << "Group id: #{id}"
  158. desc << "\nDescription: #{description}" if description
  159. desc << "\nEndianness: #{endian}"
  160. unless size == 0
  161. desc << ''
  162. desc << 'Pins'
  163. desc << '-------'
  164. if display == :id
  165. desc << map(&:id).join(', ')
  166. elsif display == :name
  167. desc << map(&:name).join(', ')
  168. else
  169. fail 'Error: Argument options for describe method are :id and :name. Default is :id'
  170. end
  171. end
  172. desc << '********************'
  173. puts desc.join("\n")
  174. end
  175. 2 def add_pin(pin, _options = {})
  176. 3961 if pin.is_a?(PinCollection)
  177. # Need this to bypass the endianness aware iteration, the storing order
  178. # is always the same. So can't use each and co here.
  179. 183 pin.size.times do |i|
  180. 732 pin[i].invalidate_group_cache
  181. 732 @store.push(pin[i])
  182. end
  183. else
  184. # Convert any named reference to a pin object
  185. 3778 if power_pins?
  186. 29 pin = owner.power_pins(pin)
  187. 3749 elsif ground_pins?
  188. 41 pin = owner.ground_pins(pin)
  189. 3708 elsif other_pins?
  190. 5 pin = owner.other_pins(pin)
  191. 3703 elsif virtual_pins?
  192. 5 pin = owner.virtual_pins(pin)
  193. else
  194. 3698 pin = owner.pins(pin)
  195. end
  196. 3778 unless @store.include?(pin)
  197. 3778 pin.invalidate_group_cache
  198. 3778 @store.push(pin)
  199. end
  200. end
  201. end
  202. 2 alias_method :<<, :add_pin
  203. 2 def drive(val)
  204. 215 value = clean_value(value)
  205. 215 each_with_index do |pin, i|
  206. 1652 pin.drive(val[size - i - 1])
  207. end
  208. 215 myself
  209. end
  210. 2 alias_method :write, :drive
  211. 2 def drive!(val)
  212. 145 drive(val)
  213. 145 cycle
  214. end
  215. 2 alias_method :write!, :drive!
  216. # Set all pins in pin group to drive 1's on future cycles
  217. 2 def drive_hi
  218. 7 each(&:drive_hi)
  219. 7 myself
  220. end
  221. 2 alias_method :write_hi, :drive_hi
  222. 2 def drive_hi!
  223. 4 drive_hi
  224. 4 cycle
  225. end
  226. 2 alias_method :write_hi!, :drive_hi!
  227. # Set all pins in pin group to drive 0's on future cycles
  228. 2 def drive_lo
  229. 11 each(&:drive_lo)
  230. 11 myself
  231. end
  232. 2 alias_method :write_lo, :drive_lo
  233. 2 def drive_lo!
  234. 4 drive_lo
  235. 4 cycle
  236. end
  237. 2 alias_method :write_lo!, :drive_lo!
  238. # Set all pins in the pin group to drive a high voltage on future cycles (if the tester supports it).
  239. # For example on a J750 high-voltage channel the pin state would be set to "2"
  240. 2 def drive_very_hi
  241. 5 each(&:drive_very_hi)
  242. 5 myself
  243. end
  244. 2 def drive_very_hi!
  245. 4 drive_very_hi
  246. 4 cycle
  247. end
  248. 2 def drive_mem
  249. 5 each(&:drive_mem)
  250. 5 myself
  251. end
  252. 2 def drive_mem!
  253. 2 drive_mem
  254. 2 cycle
  255. end
  256. 2 def expect_mem
  257. 5 each(&:expect_mem)
  258. 5 myself
  259. end
  260. 2 def expect_mem!
  261. 2 expect_mem
  262. 2 cycle
  263. end
  264. # Returns the data value held by the collection
  265. # ==== Example
  266. # pins(:porta).write(0x55)
  267. # pins(:porta).data # => 0x55
  268. 2 def data
  269. 29 data = 0
  270. 233 each_with_index { |pin, i| data |= pin.data << (size - i - 1) }
  271. 29 data
  272. end
  273. 2 alias_method :val, :data
  274. 2 alias_method :value, :data
  275. # Returns the inverse of the data value held by the collection
  276. 2 def data_b
  277. # (& operation takes care of Bignum formatting issues)
  278. 6 ~data & ((1 << size) - 1)
  279. end
  280. 2 def toggle
  281. 41 each(&:toggle)
  282. 41 myself
  283. end
  284. 2 def toggle!
  285. 9 toggle
  286. 9 cycle
  287. end
  288. 2 def repeat_previous=(bool)
  289. 4 each { |pin| pin.repeat_previous = bool }
  290. 1 myself
  291. end
  292. # Mark the (data) from all the pins in the pin group to be captured
  293. 2 def capture
  294. 5 each(&:capture)
  295. 5 myself
  296. end
  297. 2 alias_method :store, :capture
  298. 2 def capture!
  299. capture
  300. cycle
  301. end
  302. 2 alias_method :store!, :capture!
  303. 2 def restore_state
  304. 3 save
  305. 3 yield
  306. 3 restore
  307. end
  308. 2 def save
  309. 3 each(&:save)
  310. end
  311. 2 def restore
  312. 3 each(&:restore)
  313. end
  314. 2 def id=(val)
  315. 1123 @id = val.to_sym
  316. end
  317. 2 def cycle
  318. 189 Origen.tester.cycle
  319. end
  320. 2 def assert(value, options = {})
  321. 26 value = clean_value(value)
  322. 26 each_with_index do |pin, i|
  323. 184 if !value.respond_to?('data')
  324. 176 pin.assert(value[size - i - 1], options)
  325. 8 elsif value[size - i - 1].is_to_be_read?
  326. 4 pin.assert(value[size - i - 1].data, options)
  327. else
  328. 4 pin.dont_care
  329. end
  330. end
  331. 26 myself
  332. end
  333. 2 alias_method :compare, :assert
  334. 2 alias_method :expect, :assert
  335. 2 alias_method :read, :assert
  336. 2 def assert!(*args)
  337. 7 assert(*args)
  338. 7 cycle
  339. end
  340. 2 alias_method :compare!, :assert!
  341. 2 alias_method :expect!, :assert!
  342. 2 alias_method :read!, :assert!
  343. # Set all pins in the pin group to expect 1's on future cycles
  344. 2 def assert_hi(options = {})
  345. 54 each { |pin| pin.assert_hi(options) }
  346. 6 myself
  347. end
  348. 2 alias_method :expect_hi, :assert_hi
  349. 2 alias_method :compare_hi, :assert_hi
  350. 2 alias_method :read_hi, :assert_hi
  351. 2 def assert_hi!
  352. 4 assert_hi
  353. 4 cycle
  354. end
  355. 2 alias_method :expect_hi!, :assert_hi!
  356. 2 alias_method :compare_hi!, :assert_hi!
  357. 2 alias_method :read_hi!, :assert_hi!
  358. # Set all pins in the pin group to expect 0's on future cycles
  359. 2 def assert_lo(options = {})
  360. 54 each { |pin| pin.assert_lo(options) }
  361. 6 myself
  362. end
  363. 2 alias_method :expect_lo, :assert_lo
  364. 2 alias_method :compare_lo, :assert_lo
  365. 2 alias_method :read_lo, :assert_lo
  366. 2 def assert_lo!
  367. 4 assert_lo
  368. 4 cycle
  369. end
  370. 2 alias_method :expect_lo!, :assert_lo!
  371. 2 alias_method :compare_lo!, :assert_lo!
  372. 2 alias_method :read_lo!, :assert_lo!
  373. # Set all pins in the pin group to X on future cycles
  374. 2 def dont_care
  375. 7 each(&:dont_care)
  376. 7 myself
  377. end
  378. 2 def dont_care!
  379. 4 dont_care
  380. 4 cycle
  381. end
  382. 2 def inverted?
  383. 2 all?(&:inverted?)
  384. end
  385. 2 def comparing?
  386. 4 all?(&:comparing?)
  387. end
  388. 2 def comparing_mem?
  389. 2 all?(&:comparing_mem?)
  390. end
  391. 2 def driving?
  392. 4 all?(&:driving?)
  393. end
  394. 2 def driving_mem?
  395. 1 all?(&:driving_mem?)
  396. end
  397. 2 def high_voltage?
  398. 1 all?(&:high_voltage?)
  399. end
  400. 2 def repeat_previous?
  401. 2 all?(&:repeat_previous?)
  402. end
  403. # Returns true if the (data) from the pin collection is marked to be captured
  404. 2 def to_be_captured?
  405. 4 all?(&:to_be_captured?)
  406. end
  407. 2 alias_method :to_be_stored?, :to_be_captured?
  408. 2 alias_method :is_to_be_stored?, :to_be_captured?
  409. 2 alias_method :is_to_be_captured?, :to_be_captured?
  410. # Deletes all occurrences of a pin in a pin group
  411. 2 def delete(p)
  412. 1 @store.delete(p)
  413. end
  414. # Deletes the pin at a particular numeric index within the pin group
  415. 2 def delete_at(index)
  416. @store.delete_at(index)
  417. end
  418. 2 def pins(nick = :id)
  419. 1 Origen.deprecate <<-END
  420. The PinCollection#pins method is deprecated, if you want to get a list of pin IDs
  421. in the given collection just do pins(:some_group).map(&:id)
  422. Note that the pins method (confusingly) also does a sort, to replicate that:
  423. pins(:some_group).map(&:id).sort
  424. END
  425. 1 if nick == :id
  426. 1 @store.map(&:id).sort
  427. elsif nick == :name
  428. @store.map(&:name).sort
  429. end
  430. end
  431. # Delete this pingroup (myself)
  432. 2 def delete!
  433. 1 owner.delete_pin(myself)
  434. end
  435. 2 private
  436. 2 def clean_value(val)
  437. 241 return val if val.respond_to?(:contains_bits?)
  438. 240 val = val.data if val.respond_to?('data')
  439. 240 if val.is_a?(String) || val.is_a?(Symbol)
  440. Origen::Value.new(val)
  441. else
  442. 240 val
  443. end
  444. end
  445. # Cleans up indexed references to pins, e.g. makes these equal:
  446. #
  447. # pins(:pb)[0,1,2,3]
  448. # pins(:pb)[3,2,1,0]
  449. # pins(:pb)[0..3]
  450. # pins(:pb)[3..0]
  451. 2 def expand_and_order(*indexes)
  452. 199 ixs = []
  453. 199 indexes.flatten.each do |index|
  454. 216 if index.is_a?(Range)
  455. 192 if index.first > index.last
  456. 191 ixs << (index.last..index.first).to_a
  457. else
  458. 1 ixs << index.to_a
  459. end
  460. else
  461. 24 ixs << index
  462. end
  463. end
  464. 199 ixs.flatten.sort
  465. end
  466. 2 def method_missing(method, *args, &block)
  467. # Where the collection is only comprised of one pin delegate missing methods/attributes
  468. # to that pin
  469. 6 if size == 1
  470. first.send(method, *args, &block)
  471. # Send all assignment methods to all contained pins
  472. 6 elsif method.to_s =~ /.*=$/
  473. 2 each do |pin|
  474. 16 pin.send(method, *args, &block)
  475. end
  476. else
  477. 4 if block_given?
  478. fail 'Blocks are not currently supported by pin collections containing multiple pins!'
  479. else
  480. # Allow getters if all pins are the same
  481. 4 ref = first.send(method, *args)
  482. 36 if myself.all? { |pin| pin.send(method, *args) == ref }
  483. 4 ref
  484. else
  485. fail "The pins held by pin collection #{id} have different values for #{method}"
  486. end
  487. end
  488. end
  489. end
  490. end
  491. end
  492. end

lib/origen/pins/pin_common.rb

82.61% lines covered

92 relevant lines. 76 lines covered and 16 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. # Methods and attributes that are common to both pins
  4. # and pin groups
  5. 2 module PinCommon
  6. 2 extend ActiveSupport::Concern
  7. 2 included do
  8. 4 attr_reader :id
  9. 4 attr_reader :owner
  10. # Returns a hash containing the chip packages that the given pin is present in and a metadata hash for each package option containing
  11. # information like the location or pin number for the given in pin in the given package.
  12. 4 attr_reader :packages
  13. # Returns a hash containing the chip modes that the given pin is present in and a metadata hash for storing any information
  14. # specific to the operation of the given pin in that mode
  15. 4 attr_reader :modes
  16. # Returns a hash containing the chip configurations that the given pin is present in and a metadata hash for storing any information
  17. # specific to the operation of the given pin in that configuration
  18. 4 attr_reader :configurations
  19. # Override the chip-level configuration attribute for the given pin
  20. 4 attr_accessor :configuration
  21. # Free format field to store an description of the pin or pin group function
  22. 4 attr_accessor :description
  23. end
  24. 2 def to_sym
  25. 3056 id
  26. end
  27. # The ID of a pin should be considered immutable, however internally it may be neccessary
  28. # to change the initial ID as the pins are initially setup
  29. #
  30. # @api private
  31. 2 def id=(val)
  32. 4143 if @id && @finalized
  33. fail 'The ID of a pin cannot be changed once it has been set!'
  34. else
  35. 4143 @id = val
  36. end
  37. end
  38. # @api private
  39. 2 def finalize
  40. 7440 @finalized = true
  41. end
  42. # Returns true if the pin is enabled by the current or given context
  43. 2 def enabled?(options = {})
  44. 77628 present_in_package?(options) # && enabled_in_mode?(options) && enabled_in_configuration?(options)
  45. end
  46. # Returns true if the pin or pin group is present in the current package context.
  47. #
  48. # A pin is considered enabled when either no package context is set (all pins available
  49. # at die level), or when a package context is set and it matches one attached to the pin
  50. 2 def enabled_in_package?(options = {})
  51. 77628 package = options[:package] || current_package_id
  52. 77628 if package
  53. 108 !!(packages[:all] || packages[package])
  54. else
  55. 77520 true
  56. end
  57. end
  58. 2 alias_method :present_in_package?, :enabled_in_package?
  59. # Returns true if the pin or pin group is present in the current mode context.
  60. 2 def enabled_in_mode?(options = {})
  61. mode = options[:mode] || current_mode_id
  62. if mode
  63. !!(modes[:all] || modes.empty? || modes[mode])
  64. # If no mode is specified a pin is only available if it does not have a mode constraint
  65. else
  66. !!(modes[:all] || modes.empty?)
  67. end
  68. end
  69. # Returns true if the pin or pin group is present in the current configuration context.
  70. 2 def enabled_in_configuration?(options = {})
  71. config = options[:configuration] || current_configuration
  72. if config
  73. !!(configurations[:all] || configurations.empty? || configurations[config])
  74. # If no configuration is specified a pin is only available if it does not have a configuration constraint
  75. else
  76. !!(configurations[:all] || configurations.empty?)
  77. end
  78. end
  79. # Make the pin available in the given package, any options that are supplied will be
  80. # saved as metadata associated with the given pin in that package
  81. 2 def add_package(id, options = {})
  82. 65 packages[id] = options
  83. 65 if is_a?(Pin)
  84. 55 add_location(options[:location], package: id) if options[:location]
  85. end
  86. end
  87. # Make the pin or pin group available in the given mode, any options that are supplied will be
  88. # saved as metadata associated with the given pin in that mode
  89. 2 def add_mode(id, options = {})
  90. 2 modes[id] = options
  91. end
  92. # Make the pin or pin group available in the given configuration, any options that are supplied will be
  93. # saved as metadata associated with the given pin in that configuration
  94. 2 def add_configuration(id, options = {})
  95. configurations[id] = options
  96. end
  97. 2 private
  98. 2 def on_init(owner, options = {})
  99. 8774 @owner = owner
  100. 8774 @description = options[:description]
  101. 8774 apply_initial_scope(options)
  102. end
  103. # Returns the current configuration context for this pin/pin group, if a configuration has been
  104. # explicitly set on this pin that will be returned, otherwise the current chip-level configuration
  105. # context will be returned (nil if none is set)
  106. 2 def current_configuration
  107. 10344 configuration || begin
  108. 10342 if Origen.top_level
  109. 10342 Origen.top_level.current_configuration
  110. end
  111. end
  112. end
  113. # Returns the current top-level package ID, nil if none is set.
  114. 2 def current_package_id
  115. 87866 if Origen.top_level && Origen.top_level.current_package
  116. 156 Origen.top_level.current_package.id
  117. end
  118. end
  119. # Returns the current top-level mode ID, nil if none is set.
  120. 2 def current_mode_id
  121. 14235 if Origen.top_level && Origen.top_level.current_mode
  122. 30 Origen.top_level.current_mode.id
  123. end
  124. end
  125. 2 def apply_initial_scope(options)
  126. 8774 @packages = {}
  127. 8774 @modes = {}
  128. 8774 @configurations = {}
  129. 8774 add_initial_packages(options)
  130. 8774 add_initial_modes(options)
  131. 8774 add_initial_configurations(options)
  132. end
  133. # Returns an array containing the package ids resolved from the given options or
  134. # the current top-level context
  135. 2 def resolve_packages(options = {})
  136. 10363 [options.delete(:package) || options.delete(:packages) || current_package_id].flatten.compact
  137. end
  138. # Returns an array containing the mode ids resolved from the given options or
  139. # the current top-level context
  140. 2 def resolve_modes(options = {})
  141. 10354 [options.delete(:mode) || options.delete(:modes) || current_mode_id].flatten.compact
  142. end
  143. # Returns an array containing the configuration ids resolved from the given options or
  144. # the current top-level context
  145. 2 def resolve_configurations(options = {})
  146. 10354 [options.delete(:configuration) || options.delete(:configurations) || current_configuration].flatten.compact
  147. end
  148. 2 def add_initial_packages(options)
  149. 8774 resolve_packages(options).each do |package|
  150. 52 if package.is_a?(Hash)
  151. 23 package.each do |id, attributes|
  152. 36 add_package(id, attributes)
  153. end
  154. else
  155. 29 add_package(package)
  156. end
  157. end
  158. end
  159. 2 def add_initial_modes(options)
  160. 8774 resolve_modes(options).each do |mode|
  161. 2 if mode.is_a?(Hash)
  162. mode.each do |id, attributes|
  163. add_mode(id, attributes)
  164. end
  165. else
  166. 2 add_mode(mode)
  167. end
  168. end
  169. end
  170. 2 def add_initial_configurations(options)
  171. 8774 resolve_configurations(options).each do |config|
  172. if config.is_a?(Hash)
  173. config.each do |id, attributes|
  174. add_configuration(id, attributes)
  175. end
  176. else
  177. add_configuration(config)
  178. end
  179. end
  180. end
  181. end
  182. end
  183. end

lib/origen/pins/power_pin.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 class PowerPin < Pin
  4. 2 attr_accessor :current_limit
  5. 2 def initialize(id, owner, options = {}) # :nodoc:
  6. 85 v = options[:voltage] || options[:voltages]
  7. 85 self.voltage = v if v
  8. 85 self.current_limit = options[:current_limit] if options[:current_limit]
  9. 85 super
  10. end
  11. # Set the operating voltage for the pin, can be a single value or an array
  12. 2 def voltage=(val)
  13. 16 @voltages = [val].flatten.uniq
  14. end
  15. # Like voltages but if there is only one voltage known then it will be returned
  16. # directly instead of being wrapped in an array.
  17. # If no voltages are known this returns nil whereas voltages will return an
  18. # empty array.
  19. # For more than one voltages present this behaves like an alias of voltages.
  20. 2 def voltage
  21. 12 if voltages.size > 0
  22. 8 if voltages.size > 1
  23. 1 voltages
  24. else
  25. 7 voltages.first
  26. end
  27. end
  28. end
  29. # Returns an array of known operating voltages for the given pin
  30. 2 def voltages
  31. 31 @voltages ||= []
  32. end
  33. end
  34. end
  35. end

lib/origen/pins/timing/timeset.rb

90.22% lines covered

92 relevant lines. 83 lines covered and 9 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 module Timing
  4. 2 class Timeset
  5. 2 attr_reader :id
  6. # Returns an array containing the defined waves for drive cycles.
  7. # The wave at position 0 will be applied be default to any pin which
  8. # does not otherwise have a specific wave assignment.
  9. 2 attr_reader :drive_waves
  10. # Returns an array containing the defined waves for compare cycles
  11. # The wave at position 0 will be applied be default to any pin which
  12. # does not otherwise have a specific wave assignment.
  13. 2 attr_reader :compare_waves
  14. 2 def initialize(id)
  15. 34 @id = id
  16. 34 @drive_waves = []
  17. 34 @compare_waves = []
  18. # Look up tables that map pins to waves
  19. 34 @compare_pin_map = {}
  20. 34 @drive_pin_map = {}
  21. # Temporary storage of pin assignments
  22. 34 @pin_ids = { drive: [], compare: [] }
  23. # Create the default waves, these can be overridden later
  24. 34 wave do |w|
  25. 34 w.compare :data, at: 'period / 2'
  26. end
  27. 34 wave do |w|
  28. 34 w.drive :data, at: 0
  29. end
  30. end
  31. # Add a new drive or compare wave to the timeset
  32. #
  33. # timeset.wave :tck do |w|
  34. # w.drive :data, at: 0
  35. # w.drive 0, at: 25
  36. # w.dont_care at: "period - 10"
  37. # end
  38. 2 def wave(*pin_ids)
  39. 127 options = pin_ids.last.is_a?(Hash) ? pin_ids.pop : {}
  40. 127 w = Wave.new(self, options)
  41. 127 yield w
  42. 127 if w.drive?
  43. 75 if pin_ids.empty?
  44. 34 w.send(:index=, 0)
  45. 34 drive_waves[0] = w
  46. 34 @pin_ids[:drive][0] = pin_ids
  47. else
  48. 41 w.send(:index=, drive_waves.size)
  49. 41 drive_waves << w
  50. 41 @pin_ids[:drive] << pin_ids
  51. end
  52. else
  53. 52 if pin_ids.empty?
  54. 43 w.send(:index=, 0)
  55. 43 compare_waves[0] = w
  56. 43 @pin_ids[:compare][0] = pin_ids
  57. else
  58. 9 w.send(:index=, compare_waves.size)
  59. 9 compare_waves << w
  60. 9 @pin_ids[:compare] << pin_ids
  61. end
  62. end
  63. end
  64. 2 alias_method :compare_wave, :wave
  65. 2 alias_method :drive_wave, :wave
  66. # The timeset will cache a view of the dut's pins for performance,
  67. # calling this method will clear that cache and regenerate the internal
  68. # view. This should generally not be required, but available for corner cases
  69. # where a pin is added to the dut after the cache has been generated.
  70. 2 def clear_cache
  71. @all_pin_ids = nil
  72. @groups = nil
  73. compare_waves.each { |w| w.send(:clear_cache) }
  74. drive_waves.each { |w| w.send(:clear_cache) }
  75. end
  76. 2 private
  77. # The pin assignments are done lazily to cater for the guy who will want
  78. # to define waves ahead of pins or some such
  79. 2 def assign_pins
  80. 6 @pin_ids[:drive].each_with_index do |ids, i|
  81. 13 expand_groups(ids) do |id|
  82. 22 @drive_pin_map[id] ||= []
  83. 22 @drive_pin_map[id] << i
  84. end
  85. end
  86. 6 @pin_ids[:compare].each_with_index do |ids, i|
  87. 7 expand_groups(ids) do |id|
  88. 1 @compare_pin_map[id] ||= []
  89. 1 @compare_pin_map[id] << i
  90. end
  91. end
  92. 6 @pin_ids = :done
  93. end
  94. 2 def expand_groups(ids)
  95. 20 ids.each do |id|
  96. 8 if g = dut.pin_groups[id]
  97. 1 g.each do |pin|
  98. 16 yield pin.id
  99. end
  100. else
  101. 7 yield id
  102. end
  103. end
  104. end
  105. 2 def wave_for(pin, options)
  106. 38 assign_pins unless @pin_ids == :done
  107. 38 if options[:type] == :drive
  108. 20 i = @drive_pin_map[pin.id]
  109. 20 if i
  110. 19 if i.size > 1
  111. 3 code = options[:code]
  112. 3 code = nil if code == 1
  113. 3 code = nil if code == 0
  114. 3 i.each do |ix|
  115. 4 return drive_waves[ix] if drive_waves[ix].code == code
  116. end
  117. else
  118. 16 drive_waves[i[0]]
  119. end
  120. else
  121. 1 drive_waves[0]
  122. end
  123. else
  124. 18 i = @compare_pin_map[pin.id]
  125. 18 if i
  126. 1 if i.size > 1
  127. code = options[:code]
  128. code = nil if code == 'L' || code == :L
  129. code = nil if code == 'H' || code == :H
  130. i.each do |ix|
  131. return drive_waves[ix] if drive_waves[ix].code == code
  132. end
  133. else
  134. 1 compare_waves[i[0]]
  135. end
  136. else
  137. 17 compare_waves[0]
  138. end
  139. end
  140. end
  141. 2 def pin_ids_for(wave)
  142. 6 assign_pins unless @pin_ids == :done
  143. 6 map = wave.drive? ? @drive_pin_map : @compare_pin_map
  144. 6 if wave.index == 0
  145. 84 all_pin_ids.select { |id| !map[id] || map[id].include?(0) }
  146. else
  147. 42 all_pin_ids.select { |id| map[id] && map[id].include?(wave.index) }
  148. end
  149. end
  150. 2 def all_pin_ids
  151. 6 @all_pin_ids ||= dut.pins.values.map(&:id)
  152. end
  153. end
  154. end
  155. end
  156. end

lib/origen/pins/timing/wave.rb

86.42% lines covered

81 relevant lines. 70 lines covered and 11 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 module Timing
  4. 2 class Wave
  5. 2 attr_reader :events, :timeset, :index
  6. # Returns the pattern code value associated with the wave. By default this will return nil
  7. # if no code was given at the time the wave was defined, which means it is the wave that will
  8. # be applied for the conventional code values of 0, 1, H, L.
  9. 2 attr_reader :code
  10. 2 VALID_DRIVE_DATA = [0, 1, :data]
  11. 2 VALID_COMPARE_DATA = [0, 1, :data]
  12. 2 def initialize(timeset, options = {})
  13. 127 @code = options[:code]
  14. 127 @code = nil if [0, 1, 'H', 'L', :H, :L].include?(@code)
  15. 127 @timeset = timeset
  16. 127 @events = []
  17. end
  18. # Returns the events array but with any formula based times
  19. # evaluated.
  20. # Note that this does not raise an error if the period is not currently
  21. # set, in that case any events that reference it will have nil for
  22. # their time.
  23. 2 def evaluated_events
  24. 3 if dut.current_timeset_period
  25. 12 events.map { |e| [calc.evaluate(e[0], period: dut.current_timeset_period).ceil, e[1]] }
  26. else
  27. fail 'The current timeset period has not been set'
  28. end
  29. end
  30. # Returns an array containing all dut pin_ids that
  31. # are assigned to this wave by the parent timeset
  32. 2 def pin_ids
  33. 6 @pins_ids ||= timeset.send(:pin_ids_for, self)
  34. end
  35. # Returns an array containing all dut pin objects that
  36. # are assigned to this wave by the parent timeset
  37. 2 def pins
  38. 89 @pins ||= pin_ids.map { |id| dut.pin(id) }
  39. end
  40. 2 def drive(data, options)
  41. 118 self.type = :drive
  42. 118 validate_data(data) do |d|
  43. 118 validate_time(options) do |t|
  44. 118 events << [t, d]
  45. end
  46. end
  47. end
  48. 2 def compare(data, options)
  49. 52 self.type = :compare
  50. 52 validate_data(data) do |d|
  51. 52 validate_time(options) do |t|
  52. 52 events << [t, d]
  53. end
  54. end
  55. end
  56. 2 alias_method :compare_edge, :compare
  57. 2 def dont_care(options)
  58. 9 self.type = :drive
  59. 9 validate_time(options) do |t|
  60. 9 events << [t, :x]
  61. end
  62. end
  63. 2 alias_method :highz, :dont_care
  64. 2 def type
  65. @type ||= :drive
  66. end
  67. 2 def drive?
  68. 303 @type == :drive
  69. end
  70. 2 def compare?
  71. @type == :compare
  72. end
  73. 2 private
  74. 2 def clear_cache
  75. @pin_ids = nil
  76. @pins = nil
  77. end
  78. 2 def index=(val)
  79. 127 @index = val
  80. end
  81. 2 def validate_data(data)
  82. 170 valid = drive? ? VALID_DRIVE_DATA : VALID_COMPARE_DATA
  83. 170 data = :data if :data == :pattern
  84. 170 unless valid.include?(data)
  85. fail "Uknown data value #{data}, only these are valid: #{valid.join(', ')}"
  86. end
  87. 170 yield data
  88. end
  89. 2 def calc
  90. 141 return @calc if @calc
  91. 66 require 'dentaku'
  92. 66 @calc = Dentaku::Calculator.new
  93. end
  94. 2 def type=(t)
  95. 179 if @type
  96. 52 if @type != t
  97. fail 'Timing waves cannot both drive and compare within a cycle period!'
  98. end
  99. else
  100. 127 @type = t
  101. end
  102. end
  103. 2 def validate_time(options)
  104. 179 unless options[:at]
  105. fail 'When defining a wave event you must supply the time via the option :at'
  106. end
  107. 179 t = options[:at]
  108. 179 if t.is_a?(String)
  109. 66 d = calc.dependencies(t) - %w(period period_in_ns)
  110. 66 unless d.empty?
  111. fail "Wave time formulas can only include the variable 'period' (or 'period_in_ns'), this variable is not allowed: #{d}"
  112. end
  113. 66 t = t.gsub('period_in_ns', 'period')
  114. 66 unless calc.evaluate(t, period: 100)
  115. fail "There appears to be an error in the formula: #{t}"
  116. end
  117. 66 yield t
  118. 66 return
  119. 113 elsif t.is_a?(Numeric)
  120. 113 yield t
  121. 113 return
  122. end
  123. fail 'The :at option in a wave event definition must be either a number or a string'
  124. end
  125. end
  126. end
  127. end
  128. end

lib/origen/pins/virtual_pin.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Pins
  3. 2 class VirtualPin < Pin
  4. 2 def type=(value)
  5. 2 @type = value
  6. end
  7. end
  8. end
  9. end

lib/origen/ports/bit_collection.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Ports
  3. 2 class BitCollection < Registers::BitCollection
  4. end
  5. end
  6. end

lib/origen/ports/port.rb

88.61% lines covered

79 relevant lines. 70 lines covered and 9 lines missed.
    
  1. 2 module Origen
  2. 2 module Ports
  3. 2 class Port
  4. 2 include Netlist::Connectable
  5. 2 attr_reader :size
  6. 2 attr_reader :parent
  7. 2 attr_reader :id
  8. 2 attr_reader :type
  9. 2 alias_method :name, :id
  10. 2 alias_method :owner, :parent
  11. 2 def initialize(parent, id, options = {})
  12. 80 @size = options[:size] || 1
  13. 80 @parent = parent
  14. 80 @id = id
  15. 80 @type = options[:type]
  16. 80 @bit_names = {}.with_indifferent_access
  17. end
  18. 2 def inspect
  19. 1 "<#{self.class}:#{object_id} id:#{id} path:#{path}>"
  20. end
  21. 2 def describe(options = {})
  22. 2 desc = ['********************']
  23. 2 desc << "Port id: #{id}"
  24. 2 desc << "Port path: #{path}"
  25. 2 desc << ''
  26. 2 desc << 'Connections'
  27. 2 desc << '-----------'
  28. 2 desc << ''
  29. 2 table = netlist.table
  30. 2 ((size - 1)..0).to_a.each do |i|
  31. 16 if table[path]
  32. c = [table[path]['*'], table[path][i]].flatten.compact.map { |n| n.is_a?(Proc) ? 'Proc' : n }
  33. desc << "#{i} - #{c.shift}"
  34. c.each do |n|
  35. desc << " - #{n}"
  36. end
  37. else
  38. 16 desc << "#{i} - none"
  39. end
  40. end
  41. 2 desc << ''
  42. 2 if options[:return]
  43. 1 desc
  44. else
  45. 1 puts desc.join("\n")
  46. end
  47. end
  48. 2 def path
  49. 517 if parent.path.empty?
  50. 159 id.to_s
  51. else
  52. 358 "#{parent.path}.#{id}"
  53. end
  54. end
  55. 2 def bits(index, name, options = {})
  56. 2 if @defining
  57. 2 @bit_names[name] = index
  58. else
  59. fail 'Cannot add additional port bits once the port definition is complete'
  60. end
  61. end
  62. # Prevent infinite loop if a child bit collection checks bit_order
  63. 2 def bit_order
  64. parent.bit_order
  65. end
  66. 2 def drive(value = nil, options = {})
  67. 25 value, options = nil, value if value.is_a?(Hash)
  68. 25 if options[:index]
  69. 2 if options[:index].is_a?(Integer)
  70. 2 drive_values[options[:index]] = value ? value[0] : nil
  71. else
  72. options[:index].to_a.each do |i|
  73. drive_values[i] = value ? value[i] : nil
  74. end
  75. end
  76. else
  77. 23 size.times do |i|
  78. 65 drive_values[i] = value ? value[i] : nil
  79. end
  80. end
  81. 25 @drive_value = value
  82. end
  83. 2 def drive_values
  84. 1004 @drive_values ||= Array.new(size)
  85. end
  86. 2 def to_section
  87. 171 Section.new(self, (size - 1)..0)
  88. end
  89. 2 def method_missing(method, *args, &block)
  90. 174 if @bit_names.key?(method)
  91. 3 Section.new(self, @bit_names[method])
  92. 171 elsif BitCollection.instance_methods.include?(method)
  93. 171 to_bc.send(method, *args, &block)
  94. else
  95. super
  96. end
  97. end
  98. 2 def respond_to?(*args)
  99. 228 @bit_names.key?(args.first) || super(*args) ||
  100. BitCollection.instance_methods.include?(args.first)
  101. end
  102. 2 def [](val)
  103. 772 Section.new(self, val)
  104. end
  105. 2 def to_bc
  106. 171 to_section.to_bc
  107. end
  108. 2 private
  109. 2 def defining
  110. 2 @defining = true
  111. 2 yield
  112. 2 @defining = false
  113. end
  114. end
  115. end
  116. end

lib/origen/ports/port_collection.rb

90.91% lines covered

11 relevant lines. 10 lines covered and 1 lines missed.
    
  1. 2 module Origen
  2. 2 module Ports
  3. 2 class PortCollection < ::Hash
  4. 2 def add(name, port)
  5. 80 self[name] = port
  6. 80 by_type[port.type] ||= []
  7. 80 by_type[port.type] << port
  8. end
  9. 2 def by_type
  10. 161 @by_type ||= {}.with_indifferent_access
  11. end
  12. 2 def inspect
  13. map { |k, _v| k }.inspect
  14. end
  15. end
  16. end
  17. end

lib/origen/ports/section.rb

90.63% lines covered

64 relevant lines. 58 lines covered and 6 lines missed.
    
  1. 2 module Origen
  2. 2 module Ports
  3. 2 class Section
  4. 2 include Netlist::Connectable
  5. 2 attr_reader :port
  6. 2 attr_reader :index
  7. 2 def initialize(port, index)
  8. 949 @port = port
  9. 949 @index = index
  10. end
  11. 2 def size
  12. 939 size_of(index)
  13. end
  14. 2 def path
  15. 63 if index.is_a?(Range)
  16. 5 port.path + "[#{index.first}:#{index.last}]"
  17. else
  18. 58 port.path + "[#{index}]"
  19. end
  20. end
  21. 2 def parent
  22. 364 port.parent
  23. end
  24. 2 alias_method :owner, :parent
  25. 2 def id
  26. port.id
  27. end
  28. 2 def drive(value = nil)
  29. 2 port.drive(value, index: index)
  30. end
  31. 2 def drive_value
  32. 937 if size == 1
  33. 937 port.drive_values[index]
  34. else
  35. fail 'drive_value is only supported for a single bit port section'
  36. end
  37. end
  38. 2 def [](index)
  39. 3 Section.new(port, align_to_port(index))
  40. end
  41. 2 def respond_to?(*args)
  42. 182 super(*args) || BitCollection.instance_methods.include?(args.first)
  43. end
  44. 2 def method_missing(method, *args, &block)
  45. 9 if BitCollection.instance_methods.include?(method)
  46. 9 to_bc.send(method, *args, &block)
  47. else
  48. super
  49. end
  50. end
  51. 2 def to_bc
  52. 180 b = BitCollection.new(port, port.id)
  53. 180 indexes = index.respond_to?(:to_a) ? index.to_a : [index]
  54. 180 indexes.reverse_each do |i|
  55. 385 b << netlist.data_bit(port.path, i)
  56. end
  57. 180 b
  58. end
  59. 2 private
  60. 2 def size_of(index)
  61. 941 if index.is_a?(Range)
  62. 3 (index.first - index.last).abs + 1
  63. else
  64. 938 1
  65. end
  66. end
  67. 2 def align_to_port(val)
  68. 3 if val.is_a?(Range)
  69. 2 nlsb = lsb + val.last
  70. 2 nmsb = nlsb + size_of(val) - 1
  71. 2 out_of_range(val) if nmsb > msb
  72. 2 i = nmsb..nlsb
  73. else
  74. 1 i = lsb + val
  75. 1 out_of_range(val) if i > msb
  76. end
  77. 3 i
  78. end
  79. 2 def out_of_range(val)
  80. fail "Requested section index (#{val}) is out of range for a port section of size #{size}"
  81. end
  82. 2 def msb
  83. 3 if index.is_a?(Range)
  84. 3 index.first
  85. else
  86. index
  87. end
  88. end
  89. 2 def lsb
  90. 3 if index.is_a?(Range)
  91. 3 index.last
  92. else
  93. index
  94. end
  95. end
  96. end
  97. end
  98. end

lib/origen/registers/bit.rb

82.27% lines covered

282 relevant lines. 232 lines covered and 50 lines missed.
    
  1. 2 module Origen
  2. 2 module Registers
  3. # Models bits within Reg objects
  4. 2 class Bit
  5. # The :access property of registers or bits can be set to any of the following
  6. # key values. Implemented refers to whether the behaviour is accurately modelled
  7. # by the Origen register model or not.
  8. #
  9. # :base is used in CrossOrigen to set the IP-XACT access type on export.
  10. #
  11. # :read and :write are used in CrossOrigen for IP-XACT export to cover 'readAction'
  12. # and 'modifiedWriteValue' attributes in the IEEE 1685-2009 schema - they do not affect
  13. # Origen Core functionality (yet?).
  14. ACCESS_CODES = {
  15. 2 ro: { implemented: false, base: 'read-only', write: nil, read: nil, writable: false, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Read-Only' },
  16. rw: { implemented: true, base: 'read-write', write: nil, read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Read-Write' },
  17. rc: { implemented: false, base: 'read-only', write: nil, read: 'clear', writable: false, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Read-only, Clear-on-read' },
  18. rs: { implemented: false, base: 'read-only', write: nil, read: 'set', writable: false, readable: true, w1c: false, set_only: false, clr_only: false, description: "Set-on-read (all bits become '1' on read)" },
  19. wrc: { implemented: false, base: 'read-write', write: nil, read: 'clear', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Writable, clear-on-read' },
  20. wrs: { implemented: false, base: 'read-write', write: nil, read: 'set', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Writable, Sets-on-read' },
  21. wc: { implemented: false, base: 'read-write', write: 'clear', read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: true, description: 'Clear-on-write' },
  22. ws: { implemented: false, base: 'read-write', write: 'set', read: nil, writable: true, readable: true, w1c: false, set_only: true, clr_only: false, description: 'Set-on-write' },
  23. wsrc: { implemented: false, base: 'read-write', write: 'set', read: 'clear', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Set-on-write, clear-on-read' },
  24. wcrs: { implemented: false, base: 'read-write', write: 'clear', read: 'set', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Clear-on-write, set-on-read' },
  25. w1c: { implemented: false, base: 'read-write', write: 'oneToClear', read: nil, writable: true, readable: true, w1c: true, set_only: false, clr_only: false, description: "Write '1' to clear bits" },
  26. w1s: { implemented: false, base: 'read-write', write: 'oneToSet', read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '1' to set bits" },
  27. w1t: { implemented: false, base: 'read-write', write: 'oneToToggle', read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '1' to toggle bits" },
  28. w0c: { implemented: false, base: 'read-write', write: 'zeroToClear', read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '0' to clear bits" },
  29. w0s: { implemented: false, base: 'read-write', write: 'zeroToSet', read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '0' to set bits" },
  30. w0t: { implemented: false, base: 'read-write', write: 'zeroToToggle', read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '0' to toggle bits" },
  31. w1src: { implemented: false, base: 'read-write', write: 'oneToSet', read: 'clear', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '1' to set and clear-on-read" },
  32. w1crs: { implemented: false, base: 'read-write', write: 'oneToClear', read: 'set', writable: true, readable: true, w1c: true, set_only: false, clr_only: false, description: "Write '1' to clear and set-on-read" },
  33. w0src: { implemented: false, base: 'read-write', write: 'zeroToSet', read: 'clear', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '0' to set and clear-on-read" },
  34. w0crs: { implemented: false, base: 'read-write', write: 'zeroToClear', read: 'set', writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: "Write '0' to clear and set-on-read" },
  35. wo: { implemented: false, base: 'write-only', write: nil, read: nil, writable: true, readable: false, w1c: false, set_only: false, clr_only: false, description: 'Write-only' },
  36. woc: { implemented: false, base: 'write-only', write: 'clear', read: nil, writable: true, readable: false, w1c: false, set_only: false, clr_only: true, description: "When written sets the field to '0'. Read undeterministic" },
  37. worz: { implemented: false, base: 'write-only', write: nil, read: nil, writable: true, readable: false, w1c: false, set_only: false, clr_only: false, description: 'Write-only, Reads zero' },
  38. wos: { implemented: false, base: 'write-only', write: 'set', read: nil, writable: true, readable: false, w1c: false, set_only: true, clr_only: false, description: "When written sets all bits to '1'. Read undeterministic" },
  39. w1: { implemented: false, base: 'read-writeOnce', write: nil, read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Write-once. Next time onwards, write is ignored. Read returns the value' },
  40. wo1: { implemented: false, base: 'writeOnce', write: nil, read: nil, writable: true, readable: false, w1c: false, set_only: false, clr_only: false, description: 'Write-once. Next time onwards, write is ignored. Read is undeterministic' },
  41. dc: { implemented: false, base: 'read-write', write: nil, read: nil, writable: true, readable: true, w1c: false, set_only: false, clr_only: false, description: 'RW but no check' },
  42. rowz: { implemented: false, base: 'read-only', write: nil, read: 'clear', writable: false, readable: true, w1c: false, set_only: false, clr_only: false, description: 'Read-only, value is cleared on read' }
  43. }
  44. # Returns the Reg object that owns the bit
  45. 2 attr_reader :owner
  46. # Returns the integer position of the bit within the register
  47. 2 attr_reader :position
  48. # Current the data value currently held by the bit, 0 or 1
  49. 2 attr_reader :data
  50. # Returns any overlay string attached to the bit
  51. 2 attr_reader :overlay
  52. # If the bit does not read back with the same data as is written to it
  53. # then this will return true. This property can be assigned durgin the
  54. # register instantiation, e.g.
  55. # add_reg :control, 0x00, :mode => { :pos => 8, :bits => 8 },
  56. # :status => { :pos => 4, :bits => 2, :read_data_matches_write => false }
  57. 2 attr_reader :read_data_matches_write
  58. # Returns true if this bit has the sticky_overlay flag set, see Reg#sticky_overlay for
  59. # a full description. This is true by default.
  60. 2 attr_accessor :sticky_overlay
  61. # Returns true if this bit has the sticky_store flag set, see Reg#sticky_store for
  62. # a full description. This is false by default.
  63. 2 attr_accessor :sticky_store
  64. # Any feature associated with the bit/bits
  65. 2 attr_reader :feature
  66. # Returns the reset value of the bit
  67. 2 attr_accessor :reset_val
  68. 2 alias_method :reset_data, :reset_val
  69. 2 alias_method :reset_value, :reset_val
  70. # Allow modify of writable flag, bit is writeable by write method
  71. 2 attr_writer :writable
  72. # Allow modify of readable flag, bit is readable by read method
  73. 2 attr_writer :readable
  74. # Sets or returns the status of "write-one-to-clear"
  75. 2 attr_accessor :w1c
  76. # Allow modify of clr_only flag, bit can only be cleared (made 0)
  77. 2 attr_writer :clr_only
  78. # Allow modify of set_only flag, bit can only be set (made 1)
  79. 2 attr_writer :set_only
  80. # Returns read_action - whether anything happens to the bit when read
  81. 2 attr_reader :read_action
  82. # Returns mod_write_value - what write value modification occurs when written
  83. 2 attr_reader :mod_write_value
  84. # Returns true if bit depends on initial state of NVM in some way
  85. 2 attr_reader :nvm_dep
  86. # Returns true if bit is critical to starting an important operation (like a state machine)
  87. # so that it can be made not writable during basic register checks
  88. 2 attr_reader :start
  89. # Returns any application-specific meta-data attatched to the given bit
  90. 2 attr_accessor :meta
  91. 2 alias_method :meta_data, :meta
  92. 2 alias_method :metadata, :meta
  93. # Returns the access method for the given bit (a symbol), see the ACCESS_CODES constant for
  94. # the possible values this can have and their meaning
  95. 2 attr_accessor :access
  96. # Returns the basic access string for a given access method. Possible values: read-write, read-only,
  97. # write-only, writeOnce, read-writeOnce. Used primarily by CrossOrigen IP-XACT import/export.
  98. 2 attr_reader :base_access
  99. # Can be set to indicate that the current state of the bit is unknown, e.g. after reading X from a simulation
  100. 2 attr_accessor :unknown
  101. 2 def initialize(owner, position, options = {}) # rubocop:disable MethodLength
  102. options = {
  103. 16173 start: false, # whether bit starts a state machine so be careful
  104. read_data_matches_write: true,
  105. read: false,
  106. overlay: false,
  107. store: false,
  108. sticky_overlay: true,
  109. sticky_store: false,
  110. nvm_dep: false, # whether is an NVM dependent bit
  111. }.merge(options)
  112. 16173 @owner = owner
  113. 16173 @position = position
  114. 16173 @undefined = options.delete(:undefined)
  115. 16173 @reset_val = (options.delete(:res) || options.delete(:reset) || options.delete(:data) || 0)
  116. 16173 if @reset_val.is_a?(Symbol)
  117. 26 @data = 0
  118. else
  119. 16147 @reset_val &= 1 unless @reset_val.is_a?(Symbol)
  120. 16147 @data = @reset_val
  121. end
  122. 16173 access_code = options.delete(:access)
  123. # If access has been defined then none of these other attributes can be
  124. 16173 if access_code
  125. 431 conflicts = [:readable, :writable, :clr_only, :set_only, :w1c]
  126. 2586 if conflicts.any? { |k| options.key?(k) }
  127. puts 'The following attributes cannot be set in combination with :access'
  128. puts " #{conflicts.join(', ')}"
  129. puts ''
  130. puts 'Use :access to defined the required behavior, the above attributes will be deprecated in future.'
  131. puts ''
  132. fail 'Conflicting access!'
  133. end
  134. 431 set_access(access_code)
  135. else
  136. options = {
  137. 15742 writable: true, # whether bit is writable
  138. readable: true, # whether bit is readable
  139. clr_only: false, # whether bit is clear only
  140. set_only: false, # whether bit is set only
  141. w1c: false, # whether bit is w1c (when written to 1 immediately becomes 0)
  142. }.merge(options)
  143. 15742 @readable = options.delete(:readable)
  144. 15742 @writable = options.delete(:writable)
  145. 15742 @clr_only = options.delete(:clr_only)
  146. 15742 @set_only = options.delete(:set_only)
  147. 15742 @w1c = options.delete(:w1c)
  148. 15742 set_access_from_rw
  149. end
  150. # Would like to get this integrated with access as well
  151. 16173 @read_data_matches_write = options.delete(:read_data_matches_write)
  152. 16173 @feature = options.delete(:feature)
  153. 16173 if !!feature && @writable
  154. 102 @writable = enabled?
  155. end
  156. 16173 @path = options.delete(:path)
  157. 16173 @abs_path = options.delete(:abs_path)
  158. 16173 @start = options.delete(:start)
  159. 16173 @read = options.delete(:read)
  160. 16173 @overlay = options.delete(:overlay)
  161. 16173 @store = options.delete(:store)
  162. 16173 @update_required = false
  163. 16173 @sticky_store = options.delete(:sticky_store)
  164. 16173 @sticky_overlay = options.delete(:sticky_overlay)
  165. 16173 @nvm_dep = (options.delete(:nvm_dep) ? 1 : 0)
  166. # Delete some other noise that can be left over...
  167. 16173 options.delete(:bits)
  168. 16173 options.delete(:pos)
  169. 16173 options.delete(:position)
  170. 16173 options.delete(:data)
  171. # Whatever is left must be custom application meta-data
  172. 16173 @meta = (default_bit_metadata).merge(options)
  173. end
  174. 2 def access_codes
  175. ACCESS_CODES
  176. end
  177. 2 def set_access(value)
  178. 431 unless ACCESS_CODES.keys.include?(value)
  179. puts 'Invalid access code, must be one of these:'
  180. ACCESS_CODES.each do |code, meta|
  181. puts " :#{code}".ljust(10) + " - #{meta[:description]}"
  182. end
  183. puts ''
  184. fail 'Invalid access code!'
  185. end
  186. 431 @access = value
  187. # Set access attributes by pulling key-value pairs from ACCESS_CODES[<access>]
  188. 431 @readable = ACCESS_CODES[@access][:readable]
  189. 431 @writable = ACCESS_CODES[@access][:writable]
  190. 431 @w1c = ACCESS_CODES[@access][:w1c]
  191. 431 @set_only = ACCESS_CODES[@access][:set_only]
  192. 431 @clr_only = ACCESS_CODES[@access][:clr_only]
  193. 431 @base_access = ACCESS_CODES[@access][:base]
  194. 431 @read_action = ACCESS_CODES[@access][:read]
  195. 431 @mod_write_value = ACCESS_CODES[@access][:write]
  196. end
  197. # Set @access based on @readable and @writable
  198. 2 def set_access_from_rw
  199. 15742 if @w1c
  200. 1 @access = :w1c
  201. 15741 elsif @clr_only
  202. @access = :wc
  203. 15741 elsif @set_only
  204. @access = :ws
  205. 15741 elsif @readable && @writable
  206. 8299 @access = :rw
  207. 7442 elsif @readable
  208. 7442 @access = :ro
  209. elsif @writable && @access != :worz
  210. @access = :wo
  211. end
  212. end
  213. 2 def path_var
  214. 44 @path
  215. end
  216. 2 def abs_path
  217. 38 @abs_path
  218. end
  219. 2 ACCESS_CODES.each do |code, _meta|
  220. 56 define_method "#{code}?" do
  221. 13 !!(access == code || instance_variable_get("@#{code}"))
  222. end
  223. end
  224. # Returns any application specific metadata that has been inherited by the
  225. # given bit.
  226. # This does not account for any overridding that may have been applied to
  227. # this bit specifically however, use the meta method to get that.
  228. 2 def default_bit_metadata
  229. 39868 if owner
  230. 39573 Origen::Registers.default_bit_metadata.merge(
  231. Origen::Registers.bit_metadata[owner.owner.class] || {})
  232. else
  233. 295 Origen::Registers.default_bit_metadata
  234. end
  235. end
  236. 2 def inspect
  237. "<#{self.class}:#{object_id}>"
  238. end
  239. # Always returns 1 when asked for size, a BitCollection on the other hand will return something higher
  240. 2 def size
  241. 1
  242. end
  243. # Make this bit disappear, make it unwritable with a data value of 0
  244. 2 def delete
  245. @sticky_overlay = false
  246. @sticky_store = false
  247. clear_flags
  248. @data = 0
  249. @writable = false
  250. self
  251. end
  252. # Returns true if the bit is set (holds a data value of 1)
  253. 2 def set?
  254. 2215 @data == 1 ? true : false
  255. end
  256. # Resets the data value back to the reset value and calls Bit#clear_flags
  257. 2 def reset
  258. 2083 if @reset_val.is_a?(Symbol)
  259. 2 @data = 0
  260. else
  261. 2081 @data = @reset_val
  262. end
  263. 2083 @updated_post_reset = false
  264. 2083 clear_flags
  265. 2083 self
  266. end
  267. # Returns true if the bit object is a placeholder for bit positions that have
  268. # not been defined within the parent register
  269. 2 def undefined?
  270. 36 @undefined
  271. end
  272. # Returns true if the value of the bit is known. The value will be
  273. # unknown in cases where the reset value is undefined or determined by a memory location
  274. # and where the bit has not been written or read to a specific value yet.
  275. 2 def has_known_value?
  276. 410 !@unknown && (!@reset_val.is_a?(Symbol) || @updated_post_reset)
  277. end
  278. # Set the data value of the bit to the given value (1 or 0)
  279. # If the bit is read-only, the value of the bit can be forced with 'force: true'
  280. 2 def write(value, options = {})
  281. # If an array is written it means a data value and an overlay have been supplied
  282. # in one go...
  283. 7921 if value.is_a?(Array)
  284. overlay(value[1])
  285. value = value[0]
  286. end
  287. 7921 if (@data != value & 1 && @writable) ||
  288. (@data != value & 1 && options[:force] == true)
  289. 1258 if ((set?) && (!@set_only)) ||
  290. 957 ((!set?) && (!@clr_only))
  291. 1256 @data = value & 1
  292. 1256 @update_required = true
  293. 1256 @updated_post_reset = true
  294. end
  295. end
  296. 7921 self
  297. end
  298. # Will tag all bits for read and if a data value is supplied it
  299. # will update the expected data for when the read is performed.
  300. 2 def read(value = nil, _options = {})
  301. # First properly assign the args if value is absent...
  302. 1411 if value.is_a?(Hash)
  303. options = value
  304. value = nil
  305. end
  306. 1411 write(value) if value
  307. 1411 @read = true if @readable && @read_data_matches_write
  308. 1411 self
  309. end
  310. 2 alias_method :assert, :read
  311. # Sets the store flag attribute
  312. 2 def store
  313. 10 @store = true
  314. 10 self
  315. end
  316. # Set the overlay attribute to the supplied value
  317. 2 def overlay(value)
  318. 127 @overlay = value
  319. 127 self
  320. end
  321. # Returns the overlay attribute
  322. 2 def overlay_str
  323. 96 @overlay
  324. end
  325. # Returns true if the bit's read flag is set
  326. 2 def is_to_be_read?
  327. 5122 @read
  328. end
  329. # Returns true if the bit's store flag is set
  330. 2 def is_to_be_stored?
  331. 5059 @store
  332. end
  333. # Returns true if the overlay attribute is set, optionally supply an overlay
  334. # name and this will only return true if the overlay attribute matches that name
  335. 2 def has_overlay?(name = nil)
  336. 4368 if name
  337. name.to_s == @overlay.to_s
  338. else
  339. 4368 !!@overlay
  340. end
  341. end
  342. # Returns true if the bit is writable
  343. 2 def is_writable?
  344. 69 @writable
  345. end
  346. 2 alias_method :writable?, :is_writable?
  347. 2 def is_readable?
  348. 69 @readable
  349. end
  350. 2 alias_method :readable?, :is_readable?
  351. # Clears the read, store, overlay and update_required flags of this bit.
  352. # The store and overlay flags will not be cleared if the the bit's sticky_store
  353. # or sticky_overlay attributes are set respectively.
  354. 2 def clear_flags
  355. 5303 @read = false
  356. 5303 @store = false unless @sticky_store
  357. 5303 @overlay = false unless @sticky_overlay
  358. 5303 @update_required = false
  359. 5303 self
  360. end
  361. # Clears the read flag of this bit.
  362. 2 def clear_read_flag
  363. 33 @read = false
  364. 33 self
  365. end
  366. # Returns a bit mask for this bit, that is a 1 shifted into the position
  367. # corresponding to this bit's position. e.g. A bit with position 4 would return
  368. # %1_0000
  369. 2 def mask
  370. mask_val = 1
  371. mask_val << @position
  372. end
  373. # Returns a 'null' bit object which has value 0 and no other attributes set
  374. 2 def self.null(owner, position) # :nodoc:
  375. Bit.new(owner, position, writable: false)
  376. end
  377. # Returns the value you would need to write to the register to put the given
  378. # value in this bit
  379. 2 def setting(value)
  380. 39 value = value & 1 # As this bit can only hold one bit of data force it
  381. 39 value << @position
  382. end
  383. # Returns true if the bit's update_required flag is set, typically this will be the
  384. # case when a write has changed the data value of the bit but a BitCollection#write!
  385. # method has not been called yet to apply it to silicon
  386. 2 def update_required?
  387. 30 @update_required
  388. end
  389. # With only one bit it just returns itself
  390. 2 def shift_out_left
  391. yield self
  392. end
  393. # Returns the data shifted by the bit position
  394. 2 def data_in_position
  395. 2 data << position
  396. end
  397. # Clears any w1c bits that are set
  398. 2 def clear_w1c
  399. if @w1c && set?
  400. @data = 0
  401. end
  402. self
  403. end
  404. # Clears any start bits that are set
  405. 2 def clear_start
  406. if @start && set?
  407. @data = 0
  408. end
  409. self
  410. end
  411. 2 def respond_to?(*args) # :nodoc:
  412. 23666 sym = args.first
  413. 23666 meta_data_method?(sym) || super(sym)
  414. end
  415. # @api private
  416. 2 def meta_data_method?(method)
  417. 23695 attr_name = method.to_s.gsub(/\??=?/, '').to_sym
  418. 23695 if default_bit_metadata.key?(attr_name)
  419. 61 if method.to_s =~ /\?/
  420. [true, false].include?(default_bit_metadata[attr_name])
  421. else
  422. 61 true
  423. end
  424. else
  425. 23634 false
  426. end
  427. end
  428. 2 def extract_meta_data(method, *args)
  429. 20 method = method.to_s.sub('?', '')
  430. 20 if method =~ /=/
  431. 2 instance_variable_set("@#{method.sub('=', '')}", args.first)
  432. else
  433. 18 instance_variable_get("@#{method}") || meta[method.to_sym]
  434. end
  435. end
  436. 2 def method_missing(method, *args, &block) # :nodoc:
  437. 20 if meta_data_method?(method)
  438. 20 extract_meta_data(method, *args)
  439. else
  440. super
  441. end
  442. end
  443. # Returns true if the bit is constrained by the given/any feature
  444. 2 def enabled_by_feature?(name = nil)
  445. 406 if !name
  446. 212 !!feature
  447. else
  448. 194 if feature.class == Array
  449. 1 feature.each do |f|
  450. 2 if f == name
  451. return true
  452. end
  453. end
  454. 1 return false
  455. else
  456. 193 feature == name
  457. end
  458. end
  459. end
  460. 2 alias_method :has_feature_constraint?, :enabled_by_feature?
  461. 2 def enabled?
  462. 26904 if feature
  463. 224 value = false
  464. 224 current_owner = self
  465. 224 if feature.class == Array
  466. 1 feature.each do |f|
  467. 2 current_owner = self
  468. 2 loop do
  469. 4 if current_owner.respond_to?(:owner)
  470. 4 current_owner = current_owner.owner
  471. 4 if current_owner.respond_to?(:has_feature?)
  472. 2 if current_owner.has_feature?(f)
  473. 2 value = true
  474. 2 break
  475. end
  476. end
  477. else # if current owner does not have a owner
  478. value = false
  479. break
  480. end
  481. end # loop end
  482. 2 unless value
  483. if Origen.top_level && \
  484. Origen.top_level.respond_to?(:has_feature?) && \
  485. Origen.top_level.has_feature?(f)
  486. value = true
  487. unless value
  488. break
  489. end
  490. end
  491. end
  492. 2 unless value
  493. break # break if feature not found and return false
  494. end
  495. end # iterated through all features in array
  496. 1 return value
  497. else # if feature.class != Array
  498. 223 loop do
  499. 555 if current_owner.respond_to?(:owner)
  500. 502 current_owner = current_owner.owner
  501. 502 if current_owner.respond_to?(:has_feature?)
  502. 226 if current_owner.has_feature?(feature)
  503. 170 value = true
  504. 170 break
  505. end
  506. end
  507. else # if current owner does not have a owner
  508. 53 value = false
  509. 53 break
  510. end
  511. end # loop end
  512. 223 unless value
  513. 53 if Origen.top_level && \
  514. Origen.top_level.respond_to?(:has_feature?) && \
  515. Origen.top_level.has_feature?(feature)
  516. value = true
  517. end
  518. end
  519. 223 return value
  520. end
  521. else
  522. 26680 return true
  523. end
  524. end
  525. end
  526. end
  527. end

lib/origen/registers/bit_collection.rb

76.23% lines covered

467 relevant lines. 356 lines covered and 111 lines missed.
    
  1. 2 module Origen
  2. 2 module Registers
  3. # This is a regular Ruby array that is used to store collections of Bit objects, it has additional
  4. # methods added to allow interaction with the contained bits.
  5. # All Ruby array methods are also available - http://www.ruby-doc.org/core/classes/Array.html
  6. #
  7. # A BitCollection is returned whenever a subset of bits is requested from a register. Also whenever
  8. # any of these methods are called on a register object a BitCollection is created on the fly that
  9. # contains all bits in the register. This means that when interacting with a Register, a single Bit,
  10. # or a group of Bit objects, the same API can be used as described below.
  11. 2 class BitCollection < Array
  12. 2 include Origen::SubBlocks::Path
  13. 2 include Netlist::Connectable
  14. 2 DONT_CARE_CHAR = 'X'
  15. 2 OVERLAY_CHAR = 'V'
  16. 2 STORE_CHAR = 'S'
  17. 2 UNKNOWN_CHAR = '?'
  18. 2 attr_accessor :name
  19. 2 alias_method :id, :name
  20. 2 def initialize(reg, name, data = [], options = {}) # :nodoc:
  21. 26814 if reg.respond_to?(:has_bits_enabled_by_feature?) && reg.has_parameter_bound_bits?
  22. 170 reg.update_bound_bits unless reg.updating_bound_bits?
  23. end
  24. 26814 @reg = reg
  25. 26814 @name = name
  26. 26814 @with_bit_order = options[:with_bit_order] || :lsb0
  27. 47119 [data].flatten.each { |item| self << item }
  28. end
  29. # Returns the bit order of the parent register
  30. 2 def bit_order
  31. 2 parent.bit_order
  32. end
  33. # Returns the bit numbering order to use when interpreting indeces
  34. 2 def with_bit_order
  35. 6 @with_bit_order
  36. end
  37. # Allow bit number interpreting to be explicitly set to msb0
  38. 2 def with_msb0
  39. 28 @with_bit_order = :msb0
  40. 28 self
  41. end
  42. # Allow bit number interpreting to be explicitly set to lsb0
  43. 2 def with_lsb0
  44. 17009 if block_given?
  45. # run just the code block with lsb0 numbering (for internal methods)
  46. 378 saved_wbo = @with_bit_order
  47. 378 @with_bit_order = :lsb0
  48. 378 yield
  49. 378 @with_bit_order = saved_wbo
  50. else
  51. 16631 @with_bit_order = :lsb0
  52. 16631 self
  53. end
  54. end
  55. 2 def terminal?
  56. true
  57. end
  58. 2 def bind(live_parameter)
  59. 2 parent.bind(name, live_parameter)
  60. end
  61. # Access bits by index
  62. #
  63. # **Note** This method behaves differently depending on the setting of @with_bit_order
  64. #
  65. # If @with_bit_order == :lsb0 (default) index 0 refers to the lsb of the bit collection
  66. # If @with_bit_order == :msb0 index 0 refers to the msb of the bit collection
  67. #
  68. # ==== Example
  69. # dut.reg(:some_reg).bits(:some_field).with_msb0[0..1] # returns 2 most significant bits
  70. # dut.reg(:some_reg).bits(:some_field)[0..1] # returns 2 least significant bits
  71. #
  72. # **Note** Internal methods should call this method using a with_lsb0 block around the code
  73. # or alternatively use the shift_out methods
  74. # ==== Example
  75. # with_lsb0 do
  76. # saved_bit = [index]
  77. # [index] = some_new_bit_or_operation
  78. # end
  79. 2 def [](*indexes)
  80. 8339 return self if indexes.empty?
  81. 8338 b = BitCollection.new(parent, name)
  82. 8338 expand_and_order(*indexes).each do |i|
  83. 8429 b << fetch(i)
  84. end
  85. # When 1 bit requested just return that bit, this is consistent with the original
  86. # behaviour before sub collections were added
  87. 8338 if b.size == 1
  88. 8318 b.first
  89. else
  90. # maintain downstream bit numbering setting
  91. 20 @with_bit_order == :msb0 ? b.with_msb0 : b
  92. end
  93. end
  94. 2 alias_method :bits, :[]
  95. 2 alias_method :bit, :[]
  96. 2 def parent
  97. 8476 @reg
  98. end
  99. 2 def path_var
  100. 28 if first.path_var
  101. 8 if first.path_var =~ /^\./
  102. 4 base = parent.path(relative_to: parent.parent)
  103. 4 "#{base}#{first.path_var}"
  104. else
  105. 4 first.path_var
  106. end
  107. else
  108. 20 base = parent.path(relative_to: parent.parent)
  109. 20 if size == 1
  110. 10 "#{base}[#{position}]"
  111. else
  112. 10 "#{base}[#{position + size - 1}:#{position}]"
  113. end
  114. end
  115. end
  116. 2 def abs_path
  117. 38 first.abs_path
  118. end
  119. 2 Bit::ACCESS_CODES.each do |code, _meta|
  120. 56 define_method "#{code}?" do
  121. 40 all? { |b| b.undefined? || b.send("#{code}?") }
  122. end
  123. end
  124. # Update the register contents with the live value from the device under test.
  125. #
  126. # The current tester needs to be an OrigenLink driver. Upon calling this method a request will
  127. # be made to read the given register, the read data will be captured and the register model
  128. # will be updated.
  129. #
  130. # The register parent register object is returned, this means that calling .sync on a register
  131. # or bitcollection object will automatically update it and the display the register in the
  132. # console.
  133. #
  134. # Normally this method should be called from a breakpoint during pattern debug, and it is
  135. # not intended to be inserted into production pattern logic.
  136. 2 def sync(size = nil, options = {})
  137. size, options = nil, size if size.is_a?(Hash)
  138. if tester.respond_to?(:capture)
  139. preserve_flags do
  140. v = tester.capture do
  141. store!(sync: true)
  142. end
  143. if v.first
  144. # Serial shift
  145. if v.size == 1
  146. reverse_shift_out_with_index do |bit, i|
  147. bit.instance_variable_set('@updated_post_reset', true)
  148. bit.instance_variable_set('@data', v.first[i])
  149. end
  150. # Parallel shift
  151. else
  152. reverse_shift_out_with_index do |bit, i|
  153. bit.instance_variable_set('@updated_post_reset', true)
  154. bit.instance_variable_set('@data', v[i].to_i)
  155. end
  156. end
  157. else
  158. Origen.log.warning "No data was captured when attempting to sync register #{owner.name}, this is probably because the current read_register driver method does not implement store requests"
  159. end
  160. end
  161. if size
  162. puts "#{parent.address.to_s(16).upcase}: " + data.to_s(16).upcase.rjust(Origen.top_level.memory_width / 4, '0')
  163. if size > 1
  164. step = Origen.top_level.memory_width / 8
  165. Origen.top_level.mem(parent.address + step).sync(size - 1)
  166. end
  167. nil
  168. else
  169. parent
  170. end
  171. else
  172. Origen.log.warning 'Sync is not supported on the current tester driver, register not updated'
  173. end
  174. end
  175. 2 alias_method :sync!, :sync
  176. # At the end of the given block, the status flags of all bits will be restored to the state that
  177. # they were upon entry to the block
  178. 2 def preserve_flags
  179. orig = []
  180. each do |bit|
  181. orig << [bit.overlay_str, bit.is_to_be_read?, bit.is_to_be_stored?]
  182. end
  183. yield
  184. each do |bit|
  185. bit.clear_flags
  186. flags = orig.shift
  187. bit.overlay(flags[0])
  188. bit.read if flags[1]
  189. bit.store if flags[2]
  190. end
  191. self
  192. end
  193. # Copies all data and flags from one bit collection (or reg) object to another
  194. #
  195. # This method will accept a dumb value as the argument, in which case it is essentially a write,
  196. # however it will also clear all flags.
  197. 2 def copy_all(reg)
  198. 26 if reg.respond_to?(:contains_bits?) && reg.contains_bits?
  199. 25 unless reg.size == size
  200. puts 'Bit collection copy must be performed on collections of the same size.'
  201. puts 'You can fix this by calling copy on a subset of the bits you require, e.g.'
  202. puts ' larger_bit_collection[3..0].copy_all(smaller_bit_collection)'
  203. puts
  204. fail 'Mismatched size for bit collection copy'
  205. end
  206. # safely handle collections with differing with_bit_order settings
  207. 25 with_lsb0 do
  208. 25 reg.shift_out_with_index do |source_bit, i|
  209. 760 if source_bit
  210. 760 self[i].overlay(source_bit.overlay_str) if source_bit.has_overlay?
  211. 760 self[i].write(source_bit.data)
  212. 760 self[i].read if source_bit.is_to_be_read?
  213. 760 self[i].store if source_bit.is_to_be_stored?
  214. end
  215. end
  216. end # of with_lsb0
  217. else
  218. 1 write(reg)
  219. 1 clear_flags
  220. end
  221. 26 self
  222. end
  223. # Returns the access attribute of the first contained bit, in most normal use cases
  224. # the application will naturally guarantee that when this is called all of the bits
  225. # in the collection have the same access value.
  226. #
  227. # If you are worried about hitting the case where some bits have different values then
  228. # use access!, but this will be a bit less efficient
  229. 2 def access(value = nil)
  230. 70 if value.nil?
  231. 70 first.access
  232. else # set access
  233. each { |b| b.set_access(value) }
  234. self
  235. end
  236. end
  237. # Like access but will raise an error if not all bits in the collection have the same
  238. # access value
  239. 2 def access!
  240. val = access
  241. if any? { |b| b.access != val }
  242. fail 'Not all bits the collection have the same access value!'
  243. end
  244. val
  245. end
  246. # Returns the description of the given bit(s) if any, if none then an empty array
  247. # will be returned
  248. #
  249. # **Note** Adding a description field will override any comment-driven documentation
  250. # of a bit collection (ie markdown style comments)
  251. 2 def description(bitname = nil, options = {})
  252. 58 bitname, options = nil, bitname if bitname.is_a?(Hash)
  253. 58 if name == :unknown
  254. []
  255. else
  256. 58 @reg.description(name, options)
  257. end
  258. end
  259. 2 def full_name(bitname = nil, options = {})
  260. 9 bitname, options = nil, bitname if bitname.is_a?(Hash)
  261. 9 unless name == :unknown
  262. 9 @reg.full_name(name, options)
  263. end
  264. end
  265. 2 def bit_value_descriptions(_bitname = nil)
  266. 24 options = _bitname.is_a?(Hash) ? _bitname : {}
  267. 24 if name == :unknown
  268. []
  269. else
  270. 24 @reg.bit_value_descriptions(name, options)
  271. end
  272. end
  273. # Returns a dummy bit collection that is populated with un-writable bits that will
  274. # read back as 0. This can be useful for padding out spaces in registers with something that
  275. # responds like conventional bits.
  276. 2 def self.dummy(reg, name = nil, options = {})
  277. 31 name, options = nil, name if name.is_a?(Hash)
  278. options = {
  279. 31 size: 8,
  280. pos: 0
  281. }.merge(options)
  282. 31 collection = new(reg, name)
  283. 31 pos = options[:pos]
  284. 31 options[:size].times do
  285. 86 bit = Bit.new(reg, pos, writable: false, feature: :dummy_feature)
  286. 86 collection << bit
  287. 86 pos += 1
  288. end
  289. 31 collection
  290. end
  291. 2 def contains_bits?
  292. 2 true
  293. end
  294. 2 def inspect
  295. "<#{self.class}:#{object_id}>"
  296. end
  297. # Returns the LSB position of the collection
  298. 2 def position
  299. 1081 first.position
  300. end
  301. # Returns the data value held by the collection
  302. # ==== Example
  303. # reg(:control).write(0x55)
  304. # reg(:control).data # => 0x55, assuming the reg has the required bits to store that
  305. 2 def data
  306. 3814 data = 0
  307. 3814 shift_out_with_index do |bit, i|
  308. 10185 return undefined if bit.is_a?(Origen::UndefinedClass)
  309. 10162 data |= bit.data << i
  310. end
  311. 3791 data
  312. end
  313. 2 alias_method :val, :data
  314. 2 alias_method :value, :data
  315. # Returns the inverse of the data value held by the collection
  316. 2 def data_b
  317. # (& operation takes care of Bignum formatting issues)
  318. 2 ~data & ((1 << size) - 1)
  319. end
  320. # Returns the reverse of the data value held by the collection
  321. 2 def data_reverse
  322. 2 data = 0
  323. 2 reverse_shift_out_with_index do |bit, i|
  324. 64 return undefined if bit.is_a?(Origen::UndefinedClass)
  325. 64 data |= bit.data << i
  326. end
  327. 2 data
  328. end
  329. 2 alias_method :reverse_data, :data_reverse
  330. # Supports reg.bit[0] and bitcollection.bit[0]
  331. 2 def bit
  332. 68 self
  333. end
  334. # Returns true if the collection contains all bits in the register
  335. 2 def whole_reg?
  336. size == parent.size
  337. end
  338. # Set the data value of the collection within the patgen, but not on silicon - i.e. calling
  339. # write will not trigger a pattern write event.
  340. 2 def write(value, options = {})
  341. # If an array is written it means a data value and an overlay have been supplied
  342. # in one go...
  343. 353 if value.is_a?(Array) && !value.is_a?(BitCollection)
  344. overlay(value[1])
  345. value = value[0]
  346. end
  347. 353 value = value.data if value.respond_to?('data')
  348. 353 with_lsb0 do
  349. 353 size.times do |i|
  350. 6974 self[i].write(value[i], options)
  351. end
  352. end
  353. 353 self
  354. end
  355. 2 alias_method :data=, :write
  356. 2 alias_method :value=, :write
  357. 2 alias_method :val=, :write
  358. # Sets the unknown attribute on all contained bits
  359. 2 def unknown=(val)
  360. 5 each { |bit| bit.unknown = val }
  361. end
  362. # Will tag all bits for read and if a data value is supplied it
  363. # will update the expected data for when the read is performed.
  364. 2 def read(value = nil, options = {}) # :nodoc:
  365. # First properly assign the args if value is absent...
  366. 50 if value.is_a?(Hash)
  367. options = value
  368. value = nil
  369. end
  370. 50 if value
  371. 30 value = Reg.clean_value(value)
  372. 30 write(value, force: true)
  373. end
  374. 50 if options[:mask]
  375. 42 shift_out_with_index { |bit, i| bit.read if options[:mask][i] == 1 }
  376. 42 shift_out_with_index { |bit, i| bit.clear_read_flag if options[:mask][i] == 0 }
  377. else
  378. 48 each(&:read)
  379. end
  380. 50 self
  381. end
  382. 2 alias_method :assert, :read
  383. # Returns a value representing the bit collection / register where a bit value of
  384. # 1 means the bit is enabled for the given operation.
  385. 2 def enable_mask(operation)
  386. 2 str = ''
  387. 2 shift_out_left do |bit|
  388. 32 if operation == :store && bit.is_to_be_stored? ||
  389. operation == :read && bit.is_to_be_read? ||
  390. operation == :overlay && bit.has_overlay?
  391. 4 str += '1'
  392. else
  393. 28 str += '0'
  394. end
  395. end
  396. 2 str.to_i(2)
  397. end
  398. # Attaches the supplied overlay string to all bits
  399. # ==== Example
  400. # reg(:data).overlay("data_val")
  401. 2 def overlay(value)
  402. 88 each { |bit| bit.overlay(value) }
  403. 9 self
  404. end
  405. # Resets all bits, this clears all flags and assigns the data value
  406. # back to the reset state
  407. 2 def reset
  408. 68 each(&:reset)
  409. 68 self
  410. end
  411. # Shifts out a stream of bit objects corresponding to the size of the BitCollection. i.e. calling
  412. # this on a 16-bit register this will pass back 16 bit objects.
  413. # If there are holes in the given register then a dummy bit object will be returned that
  414. # is not writable and which will always read as 0.
  415. # ==== Example
  416. # reg(:data).shift_out_left do |bit|
  417. # bist_shift(bit)
  418. # end
  419. 2 def shift_out_left
  420. # This is functionally equivalent to reverse_shift_out
  421. 1669 reverse_each { |bit| yield bit }
  422. end
  423. # Same as Reg#shift_out_left but includes the index counter
  424. 2 def shift_out_left_with_index
  425. # This is functionally equivalent to reverse_shift_out_with_index
  426. 83 reverse_each.with_index { |bit, i| yield bit, i }
  427. end
  428. # Same as Reg#shift_out_left but starts from the LSB
  429. 2 def shift_out_right
  430. # This is functionally equivalent to shift_out, actually sends LSB first
  431. 77 each { |bit| yield bit }
  432. end
  433. # Same as Reg#shift_out_right but includes the index counter
  434. 2 def shift_out_right_with_index
  435. # This is functionally equivalent to shift_out_with_index
  436. 83 each_with_index { |bit, i| yield bit, i }
  437. end
  438. # Yields each bit in the register, LSB first.
  439. 2 def shift_out(&block)
  440. 30 each(&block)
  441. end
  442. # Yields each bit in the register and its index, LSB first.
  443. 2 def shift_out_with_index(&block)
  444. 3958 each_with_index(&block)
  445. end
  446. # Yields each bit in the register, MSB first.
  447. 2 def reverse_shift_out(&block)
  448. 5 reverse_each(&block)
  449. end
  450. # Yields each bit in the register and its index, MSB first.
  451. 2 def reverse_shift_out_with_index(&block)
  452. 3 reverse_each.with_index(&block)
  453. end
  454. # Returns true if any bits have the read flag set - see Bit#is_to_be_read?
  455. # for more details.
  456. 2 def is_to_be_read?
  457. 2627 any?(&:is_to_be_read?)
  458. end
  459. # Returns true if any bits have the store flag set - see Bit#is_to_be_stored?
  460. # for more details.
  461. 2 def is_to_be_stored?
  462. 2581 any?(&:is_to_be_stored?)
  463. end
  464. # Returns true if any bits have the update_required flag set - see Bit#update_required?
  465. # for more details.
  466. 2 def update_required?
  467. 5 any?(&:update_required?)
  468. end
  469. # Calls the clear_flags method on all bits, see Bit#clear_flags for more details
  470. 2 def clear_flags
  471. 77 each(&:clear_flags)
  472. 77 self
  473. end
  474. # Returns the value you would need to write to the register to put the given
  475. # value in these bits
  476. 2 def setting(value)
  477. 9 result = 0
  478. 9 shift_out_with_index do |bit, i|
  479. 39 result |= bit.setting(value[i])
  480. end
  481. 9 result
  482. end
  483. # Returns true if any bits within are tagged for overlay, supply a specific name
  484. # to require a specific overlay only
  485. # ==== Example
  486. # myreg.overlay("data")
  487. # myreg.has_overlay? # => true
  488. # myreg.has_overlay?("address") # => false
  489. # myreg.has_overlay?("data") # => true
  490. 2 def has_overlay?(name = nil)
  491. 5412 any? { |bit| bit.has_overlay?(name) }
  492. end
  493. # Cycles through all bits and returns the last overlay value found, it is assumed therefore
  494. # that all bits have the same overlay value when calling this method
  495. # ==== Example
  496. # myreg.overlay("data")
  497. #
  498. # myreg.overlay_str # => "data"
  499. 2 def overlay_str
  500. 3 result = ''
  501. 3 each do |bit|
  502. 48 result = bit.overlay_str if bit.has_overlay?
  503. end
  504. 3 result.to_s
  505. end
  506. # Write the bit value on silicon.
  507. # This method will update the data value of the bits and then call $top.write_register
  508. # passing the owning register as the first argument.
  509. # This method is expected to handle writing the current state of the register to silicon.
  510. 2 def write!(value = nil, options = {})
  511. 16 value, options = nil, value if value.is_a?(Hash)
  512. 16 write(value, options) if value
  513. 16 if block_given?
  514. 2 yield size == @reg.size ? @reg : self
  515. end
  516. 16 @reg.request(:write_register, options)
  517. 16 self
  518. end
  519. # Similar to write! this method will perform the standard read method and then make
  520. # a call to $top.read_register(self) with the expectation that this method will
  521. # implement a read event in the pattern.
  522. # ==== Example
  523. # reg(:data).read! # Read register :data, expecting whatever value it currently holds
  524. # reg(:data).read!(0x5555) # Read register :data, expecting 0x5555
  525. 2 def read!(value = nil, options = {})
  526. 29 value, options = nil, value if value.is_a?(Hash)
  527. 29 read(value, options) unless block_given?
  528. 29 if block_given?
  529. 1 yield size == @reg.size ? @reg : self
  530. end
  531. 29 @reg.request(:read_register, options)
  532. 29 self
  533. end
  534. 2 alias_method :assert!, :read!
  535. # Normally whenever a register is processed by the $top.read_register method
  536. # it will call Reg#clear_flags to acknowledge that the read has been performed,
  537. # which clears the read and store flags for the given bits. Normally however you
  538. # want overlays to stick around such that whenever a given bit is written/read its
  539. # data is always picked from an overlay.<br>
  540. # Call this passing in false for a given register to cause the overlay data to also
  541. # be cleared by Reg#clear_flags.
  542. # ==== Example
  543. # reg(:data).overlay("data_val")
  544. # reg(:data).has_overlay? # => true
  545. # reg(:data).clear_flags
  546. # reg(:data).has_overlay? # => true
  547. # reg(:data).sticky_overlay(false)
  548. # reg(:data).clear_flags
  549. # reg(:data).has_overlay? # => false
  550. 2 def sticky_overlay(set = true)
  551. each { |bit| bit.sticky_overlay = set }
  552. self
  553. end
  554. 2 alias_method :sticky_overlays, :sticky_overlay
  555. # Similar to sticky_overlay this method affects how the store flags are treated by
  556. # Reg#clear_flags.<br>
  557. # The default is that store flags will get cleared by Reg#clear_flags, passing true
  558. # into this method will override this and prevent them from clearing.
  559. # ==== Example
  560. # reg(:data).sticky_store(true)
  561. # reg(:data).store
  562. # reg(:data).clear_flags # Does not clear the request to store
  563. 2 def sticky_store(set = true)
  564. each { |bit| bit.sticky_store = set }
  565. self
  566. end
  567. # Marks all bits to be stored
  568. 2 def store(options = {})
  569. 4 each(&:store)
  570. 4 self
  571. end
  572. # Marks all bits to be stored and then calls read!
  573. 2 def store!(options = {})
  574. store(options)
  575. read!(options)
  576. self
  577. end
  578. # Sets the store flag on all bits that already have the overlay flag set
  579. # and then calls $top.read_register passing self as the first argument
  580. 2 def store_overlay_bits!(options = {})
  581. store_overlay_bits(options)
  582. @reg.request(:read_register, options) # Bypass the normal read method since we don't want to
  583. # tag the other bits for read
  584. self
  585. end
  586. # Sets the store flag on all bits that already have the overlay flag set
  587. 2 def store_overlay_bits(options = {})
  588. options = { exclude: [], # Pass in an array of any overlays that are to be excluded from store
  589. }.merge(options)
  590. each do |bit|
  591. bit.store if bit.has_overlay? && !options[:exclude].include?(bit.overlay_str)
  592. end
  593. self
  594. end
  595. # Will yield all unique overlay strings attached to the bits within the collection.
  596. # It will also return the number of bits for the overlay (the length) and the current
  597. # data value held in those bits.
  598. # ==== Example
  599. # reg(:control).unique_overlays do |str, length, data|
  600. # do_something(str, length, data)
  601. # end
  602. 2 def unique_overlays
  603. current_overlay = false
  604. length = 0
  605. data = 0
  606. shift_out_right do |bit|
  607. # Init the current overlay when the first one is encountered
  608. current_overlay = bit.overlay_str if bit.has_overlay? && !current_overlay
  609. if bit.has_overlay?
  610. if bit.overlay_str != current_overlay
  611. yield current_overlay, length, data if current_overlay
  612. length = 0
  613. data = 0
  614. end
  615. data = data | (bit.data << length)
  616. length += 1
  617. else
  618. yield current_overlay, length, data if current_overlay
  619. length = 0
  620. data = 0
  621. current_overlay = false
  622. end
  623. end
  624. yield current_overlay, length, data if current_overlay
  625. end
  626. # Append a value, for example a block identifier, to all overlays
  627. # ==== Example
  628. # reg(:data).overlay("data_val")
  629. # reg(:data).append_overlays("_0")
  630. # reg(:data).overlay_str # => "data_val_0"
  631. 2 def append_overlays(value)
  632. each do |bit|
  633. bit.overlay(bit.overlay_str + value) if bit.has_overlay?
  634. end
  635. self
  636. end
  637. # Delete the contained bits from the parent Register
  638. 2 def delete
  639. 2 @reg.delete_bits(self)
  640. 2 self
  641. end
  642. 2 def add_name(name) # :nodoc:
  643. 4592 if @name == :unknown
  644. 677 @name = name
  645. 3915 elsif ![name].flatten.include?(name)
  646. @name = [@name, name].flatten
  647. end
  648. 4592 self
  649. end
  650. 2 def owner
  651. first.owner
  652. end
  653. # All other methods send to bit 0
  654. 2 def method_missing(method, *args, &block) # :nodoc:
  655. 58 if first.respond_to?(method)
  656. 58 if size > 1
  657. 11 if [:meta, :meta_data, :metadata].include?(method.to_sym) ||
  658. first.meta_data_method?(method)
  659. 11 first.send(method, *args, &block)
  660. else
  661. fail "Error, calling #{method} on a multi-bit collection is not implemented!"
  662. end
  663. else
  664. 47 first.send(method, *args, &block)
  665. end
  666. else
  667. fail "BitCollection does not have a method named #{method}!"
  668. end
  669. end
  670. # Recognize that BitCollection responds to some Bit methods via method_missing
  671. 2 def respond_to?(*args) # :nodoc:
  672. 3062 sym = args.first
  673. 3062 first.respond_to?(sym) || super(sym)
  674. end
  675. # Returns true if the values of all bits in the collection are known. The value will be
  676. # unknown in cases where the reset value is undefined or determined by a memory location
  677. # and where the register has not been written or read to a specific value yet.
  678. 2 def has_known_value?
  679. 71 all?(&:has_known_value?)
  680. end
  681. # Returns the reset value of the collection, note that this does not reset the register and the
  682. # current data is maintained.
  683. #
  684. # ==== Example
  685. # reg(:control).write(0x55)
  686. # reg(:control).data # => 0x55
  687. # reg(:control).reset_data # => 0x11, assuming the reg was declared with a reset value of 0x11
  688. # reg(:control).data # => 0x55
  689. 2 def reset_data(value = nil)
  690. # This method was originally setup to set the reset value by passing an argument
  691. 105 if value
  692. shift_out_with_index { |bit, i| bit.reset_val = value[i] }
  693. self
  694. else
  695. 105 data = 0
  696. 105 shift_out_with_index do |bit, i|
  697. 527 return bit.reset_data if bit.reset_data.is_a?(Symbol)
  698. 515 data |= bit.reset_data << i
  699. end
  700. 93 data
  701. end
  702. end
  703. 2 alias_method :reset_val, :reset_data
  704. 2 alias_method :reset_value, :reset_data
  705. 2 alias_method :reset_data=, :reset_data
  706. 2 alias_method :reset_val=, :reset_data
  707. 2 alias_method :reset_value=, :reset_data
  708. # Modify writable for bits in collection
  709. 2 def writable(value)
  710. shift_out_with_index { |bit, i| bit.writable = (value[i] == 0b1); bit.set_access_from_rw }
  711. self
  712. end
  713. # Modify readable for bits in collection
  714. 2 def readable(value)
  715. shift_out_with_index { |bit, i| bit.readable = (value[i] == 0b1); bit.set_access_from_rw }
  716. self
  717. end
  718. 2 def feature
  719. 4 feature = []
  720. 4 feature << fetch(0).feature
  721. 116 each { |bit| feature << bit.feature if bit.has_feature_constraint? }
  722. 4 feature = feature.flatten.uniq unless feature.empty?
  723. 4 feature.delete(nil) if feature.include?(nil)
  724. 4 if !feature.empty?
  725. 1 if feature.size == 1
  726. 1 return feature[0]
  727. else
  728. return feature.uniq
  729. end
  730. else
  731. 3 if Origen.config.strict_errors
  732. fail 'No feature found'
  733. end
  734. return nil
  735. end
  736. end
  737. 2 alias_method :features, :feature
  738. # Return true if there is any feature associated with these bits
  739. 2 def has_feature_constraint?(name = nil)
  740. 8 if !name
  741. 4 any?(&:has_feature_constraint?)
  742. else
  743. 38 any? { |bit| bit.enabled_by_feature?(name) }
  744. end
  745. end
  746. 2 alias_method :enabled_by_feature?, :has_feature_constraint?
  747. 2 def enabled?
  748. 530 all?(&:enabled?)
  749. end
  750. # Returns true if any bits in the collection are writable
  751. 2 def is_writable?
  752. 69 any?(&:writable?)
  753. end
  754. 2 alias_method :writable?, :is_writable?
  755. # Returns true if any bits in the collection are readable
  756. 2 def is_readable?
  757. 69 any?(&:readable?)
  758. end
  759. 2 alias_method :readable?, :is_readable?
  760. # Modify clr_only for bits in collection
  761. 2 def clr_only(value)
  762. shift_out_with_index { |bit, i| bit.clr_only = (value[i] == 0b1) }
  763. self
  764. end
  765. # Modify set_only for bits in collection
  766. 2 def set_only(value)
  767. shift_out_with_index { |bit, i| bit.set_only = (value[i] == 0b1) }
  768. self
  769. end
  770. # Return nvm_dep value held by collection
  771. 2 def nvm_dep
  772. nvm_dep = 0
  773. shift_out_with_index { |bit, i| nvm_dep |= bit.nvm_dep << i }
  774. nvm_dep
  775. end
  776. # Clear any w1c set bits back to 0
  777. 2 def clear_w1c
  778. each(&:clear_w1c)
  779. self
  780. end
  781. # Clear any start set bits back to 0
  782. 2 def clear_start
  783. each(&:clear_start)
  784. self
  785. end
  786. # Provides a string summary of the bit collection / register state that would be
  787. # applied to given operation (write or read).
  788. # This is mainly intended to be useful when generating pattern comments describing
  789. # an upcoming register transaction.
  790. #
  791. # This highlights not only bit values bit the status of any flags or overlays that
  792. # are currently set.
  793. #
  794. # The data is presented in hex nibble format with individual nibbles are expanded to
  795. # binary format whenever all 4 bits do not have the same status - e.g. if only one
  796. # of the four is marked for read.
  797. #
  798. # The following symbols are used to represent bit state:
  799. #
  800. # X - Bit is don't care (not marked for read)
  801. # V - Bit has been tagged with an overlay
  802. # S - Bit is marked for store
  803. #
  804. # @example
  805. #
  806. # myreg.status_str(:write) # => "0000"
  807. # myreg.status_str(:read) # => "XXXX"
  808. # myreg[7..4].read(5)
  809. # myreg.status_str(:read) # => "XX5X"
  810. # myreg[14].read(0)
  811. # myreg.status_str(:read) # => "(x0xx)X5X"
  812. 2 def status_str(operation, options = {})
  813. options = {
  814. 13 mark_overlays: true
  815. }.merge(options)
  816. 13 str = ''
  817. 13 if operation == :read
  818. 10 shift_out_left do |bit|
  819. 145 if bit.is_to_be_stored?
  820. 18 str += STORE_CHAR
  821. 127 elsif bit.is_to_be_read?
  822. 66 if bit.has_overlay? && options[:mark_overlays]
  823. 15 str += OVERLAY_CHAR
  824. else
  825. 51 if bit.has_known_value?
  826. 47 str += bit.data.to_s
  827. else
  828. 4 str += UNKNOWN_CHAR
  829. end
  830. end
  831. else
  832. 61 str += DONT_CARE_CHAR
  833. end
  834. end
  835. 3 elsif operation == :write
  836. 3 shift_out_left do |bit|
  837. 43 if bit.has_overlay? && options[:mark_overlays]
  838. 5 str += OVERLAY_CHAR
  839. else
  840. 38 if bit.has_known_value?
  841. 38 str += bit.data.to_s
  842. else
  843. str += UNKNOWN_CHAR
  844. end
  845. end
  846. end
  847. else
  848. fail "Unknown operation (#{operation}), must be :read or :write"
  849. end
  850. 13 make_hex_like(str, (size / 4.0).ceil)
  851. end
  852. # Shifts the data in the collection left by one place. The data held
  853. # by the rightmost bit will be set to the given value (0 by default).
  854. #
  855. # @example
  856. # myreg.data # => 0b1111
  857. # myreg.shift_left
  858. # myreg.data # => 0b1110
  859. # myreg.shift_left
  860. # myreg.data # => 0b1100
  861. # myreg.shift_left(1)
  862. # myreg.data # => 0b1001
  863. # myreg.shift_left(1)
  864. # myreg.data # => 0b0011
  865. 2 def shift_left(data = 0)
  866. 4 prev_bit = nil
  867. 4 reverse_shift_out do |bit|
  868. 16 prev_bit.write(bit.data) if prev_bit
  869. 16 prev_bit = bit
  870. end
  871. 4 prev_bit.write(data)
  872. 4 self
  873. end
  874. # Shifts the data in the collection right by one place. The data held
  875. # by the leftmost bit will be set to the given value (0 by default).
  876. #
  877. # @example
  878. # myreg.data # => 0b1111
  879. # myreg.shift_right
  880. # myreg.data # => 0b0111
  881. # myreg.shift_right
  882. # myreg.data # => 0b0011
  883. # myreg.shift_right(1)
  884. # myreg.data # => 0b1001
  885. # myreg.shift_right(1)
  886. # myreg.data # => 0b1100
  887. 2 def shift_right(data = 0)
  888. 29 prev_bit = nil
  889. 29 shift_out do |bit|
  890. 116 prev_bit.write(bit.data) if prev_bit
  891. 116 prev_bit = bit
  892. end
  893. 29 prev_bit.write(data)
  894. 29 self
  895. end
  896. 2 private
  897. # Converts a binary-like representation of a data value into a hex-like version.
  898. # e.g. input => 010S0011SSSS0110 (where S, X or V represent store, don't care or overlay)
  899. # output => [010s]3S6 (i.e. nibbles that are not all of the same type are expanded)
  900. 2 def make_hex_like(regval, size_in_nibbles)
  901. 13 outstr = ''
  902. 13 regex = '^(.?.?.?.)'
  903. 48 (size_in_nibbles - 1).times { regex += '(....)' }
  904. 13 regex += '$'
  905. 13 Regexp.new(regex) =~ regval
  906. 13 nibbles = []
  907. 13 size_in_nibbles.times do |n| # now grouped by nibble
  908. 48 nibbles << Regexp.last_match[n + 1]
  909. end
  910. 13 nibbles.each_with_index do |nibble, i|
  911. # If contains any special chars...
  912. 48 if nibble =~ /[#{UNKNOWN_CHAR}#{DONT_CARE_CHAR}#{STORE_CHAR}#{OVERLAY_CHAR}]/
  913. # If all the same...
  914. 28 if nibble[0] == nibble[1] && nibble[1] == nibble[2] && nibble[2] == nibble[3]
  915. 21 outstr += nibble[0, 1] # .to_s
  916. # Otherwise present this nibble in 'binary' format
  917. else
  918. 7 outstr += "[#{nibble.downcase}]"
  919. end
  920. # Otherwise if all 1s and 0s...
  921. else
  922. 20 outstr += '%1X' % nibble.to_i(2)
  923. end
  924. end
  925. 13 outstr
  926. end
  927. # Cleans up indexed references to pins, e.g. makes these equal:
  928. #
  929. # bits(:data)[0,1,2,3]
  930. # bits(:data)[3,2,1,0]
  931. # bits(:data)[0..3]
  932. # bits(:data)[3..0]
  933. 2 def expand_and_order(*indexes)
  934. 8338 ixs = []
  935. 8338 indexes.flatten.each do |index|
  936. 8338 if index.is_a?(Range)
  937. 20 if index.first > index.last
  938. 12 ixs << (index.last..index.first).to_a
  939. else
  940. 8 ixs << index.to_a
  941. end
  942. else
  943. 8318 ixs << index
  944. end
  945. end
  946. 8338 ixs.flatten!
  947. # ixs.sort!
  948. # convert msb0 numbering (if provided) to lsb0 numbering to get the correct bits
  949. 8338 if @with_bit_order == :msb0
  950. 39 ixs.each_index { |i| ixs[i] = size - ixs[i] - 1 }
  951. end
  952. 8338 ixs.sort
  953. end
  954. end
  955. end
  956. end

lib/origen/registers/container.rb

100.0% lines covered

96 relevant lines. 96 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Registers
  3. # A container can be used to easily interface register operations to an IPS-style
  4. # interface where the container will take care of data alignment and byte enable
  5. # calculations.
  6. # A container looks and behaves like a register and drivers should be able to
  7. # accept a container in place of a regular register.
  8. #
  9. # Here are some examples:
  10. #
  11. # include Origen::Registers
  12. #
  13. # # Name Address Size Bits
  14. # add_reg :r0, 4, 8, data => {:bits => 8}
  15. # add_reg :r1, 5, 8, data => {:bits => 8}
  16. # add_reg :r2, 6, 8, data => {:bits => 8}
  17. # add_reg :r3, 7, 8, data => {:bits => 8}
  18. #
  19. # reg(:r0).write(0xB0)
  20. # reg(:r1).write(0xB1)
  21. # reg(:r2).write(0xB2)
  22. # reg(:r3).write(0xB3)
  23. #
  24. # big = Container.new
  25. # little = Container.new(:endian => :little)
  26. #
  27. # big.add(reg(:r0)).data # => 0x0000_00B0
  28. # little.add(reg(:r0)).data # => 0xB000_0000
  29. # big.byte_enable # => 0b0001
  30. # little.byte_enable # => 0b1000
  31. #
  32. # big.empty
  33. # big.data # => 0x0000_0000
  34. # big.address # => nil
  35. # big.add(reg(:r2))
  36. # big.address # => 4 (longword aligned)
  37. # big.add(reg(:r3)).add(reg(:r1)
  38. # big.add.data # => 0xB3B2_B100
  39. # big.byte_enable # => 0b1110
  40. #
  41. # # Treat it like it's a register in drivers:
  42. # big.shift_out_left do |bit|
  43. # pin(:tdi).drive!(bit.data)
  44. # end
  45. #
  46. # # The address can be overridden
  47. # big.empty
  48. # big.add(reg(:r2), :address => 10)
  49. # big.address # => 8 (longword aligned)
  50. #
  51. # # Containers can accomodate other containers
  52. # big.empty
  53. # lower_word = Container.new
  54. # lower_word.add(:r0).add(:r1)
  55. # big.add(:r3)
  56. # lower_word.data # => 0x0000_B1B0
  57. # big.data # => 0xB300_0000
  58. # big.add(lower_word)
  59. # big.data # => 0xB300_B1B0
  60. # lower_word.data # => 0x0000_B1B0
  61. #
  62. # # Contained registers are the same register objects
  63. # reg(:r0).write(0x55)
  64. # big.data # => 0xB300_B155
  65. # lower_word.data # => 0x0000_B155
  66. 2 class Container
  67. # The size of the container in bits
  68. 2 attr_reader :size
  69. # The number of bits represented by an address increment
  70. # of the contained registers. For example if the contained registers
  71. # have a byte address this will return 8.
  72. 2 attr_reader :bits_per_address
  73. # Returns the currently held registers
  74. 2 attr_reader :regs
  75. 2 alias_method :registers, :regs
  76. # Set this to a string or an array of strings that represents the name of the object that owns the
  77. # container. If present any owned_by? requests made to the container will be
  78. # evaluated against this string. If not then the request will be sent to the
  79. # first contained register (if present).
  80. 2 attr_accessor :owned_by
  81. # @param [Hash] options Options to customize the container
  82. # @option options [Integer] :size (32) The size of the container in bits
  83. # @option options [Symbol] :endian (:big) The endianness of the container, :big or :little
  84. # For example big endian means that 4 a 32-bit container the bytes are arranged
  85. # [3,2,1,0] whereas a little endian container would be [0,1,2,3].
  86. # @option options [Integer] :bits_per_address (8) The number of bits that will be represented
  87. # by an address increment of the given register's addresses
  88. 2 def initialize(options = {})
  89. options = {
  90. 22 size: 32,
  91. endian: :big,
  92. bits_per_address: 8
  93. }.merge(options)
  94. 22 @size = options[:size]
  95. 22 @endian = options[:endian]
  96. 22 @owned_by = options[:owned_by]
  97. 22 @bits_per_address = options[:bits_per_address]
  98. 22 @regs = []
  99. 22 @addresses = {}
  100. end
  101. 2 def contains_bits?
  102. 1 true
  103. end
  104. # Add the given register to the container, currently there is no
  105. # error checking performed to ensure that it doesn't overlap with
  106. # any existing contained registers.
  107. 2 def add(reg, options = {})
  108. 45 @regs << reg
  109. 45 addr = options[:address] || options[:addr]
  110. 45 @addresses[reg] = addr if addr
  111. 118 @regs.sort_by! { |reg| address_of_reg(reg) }
  112. 45 self
  113. end
  114. # @api private
  115. 2 def address_of_reg(reg)
  116. 781 @addresses[reg] || reg.address
  117. end
  118. # Returns the data held by the contained registers where the data from
  119. # each register is shifted into the correct position
  120. 2 def data
  121. 17 d = 0
  122. 17 regs.each do |reg|
  123. 22 d += (reg.data << bit_shift_for_reg(reg))
  124. end
  125. 17 d
  126. end
  127. 2 alias_method :val, :data
  128. 2 alias_method :value, :data
  129. # Data bar, the ones complement of the current data value of the
  130. # container
  131. 2 def data_b
  132. 4 ~data & ((1 << size) - 1)
  133. end
  134. # Remove all registers from the container
  135. 2 def empty
  136. 6 @regs = []
  137. 6 @addresses = {}
  138. 6 self
  139. end
  140. # Returns the owner of the contained registers (assumed to be the
  141. # same for all)
  142. 2 def owner
  143. 7 unless @regs.empty?
  144. 5 @regs.first.owner
  145. end
  146. end
  147. # Proxies to the Reg#owned_by? method
  148. 2 def owned_by?(name)
  149. 3 if owned_by
  150. 1 [owned_by].flatten.any? do |al|
  151. 1 al.to_s =~ /#{name}/i
  152. end
  153. else
  154. 2 if @regs.empty?
  155. 1 false
  156. else
  157. 1 @regs.first.owned_by?(name)
  158. end
  159. end
  160. end
  161. # Returns the aligned address of the container based on the
  162. # address of the currently contained registers
  163. 2 def address
  164. 116 unless @regs.empty?
  165. 112 addr = address_of_reg(@regs.first)
  166. 112 shift = Math.log(size / bits_per_address, 2)
  167. 112 (addr >> shift) << shift
  168. end
  169. end
  170. 2 alias_method :addr, :address
  171. # Returns the byte enable required to update the contained registers.
  172. 2 def byte_enable
  173. 9 enable = 0
  174. 9 regs.each do |reg|
  175. 14 enable_bits = 0.ones_comp(reg.size / bits_per_address)
  176. 14 enable += (enable_bits << shift_for_reg(reg))
  177. end
  178. 9 enable
  179. end
  180. # @api private
  181. 2 def local_addr_for_reg(reg)
  182. 596 address_of_reg(reg) & 0.ones_comp(Math.log(size / bits_per_address, 2))
  183. end
  184. # @api private
  185. 2 def shift_for_reg(reg)
  186. 596 if big_endian?
  187. 422 local_addr_for_reg(reg)
  188. else
  189. 174 (size / bits_per_address) - (local_addr_for_reg(reg) + (reg.size / bits_per_address))
  190. end
  191. end
  192. # @api private
  193. 2 def bit_shift_for_reg(reg)
  194. 582 shift_for_reg(reg) * bits_per_address
  195. end
  196. 2 def big_endian?
  197. 602 @endian == :big
  198. end
  199. 2 def little_endian?
  200. 2 !big_endian?
  201. end
  202. # Shifts out a stream of bit objects corresponding to the size of the container. i.e. calling
  203. # this on a 32-bit container this will pass back 32 bit objects.
  204. # If there are holes then a dummy bit object will be returned that
  205. # is not writable and which will always read as 0.
  206. #
  207. # The index is also returned as a second argument. Note that
  208. # the position property of the bit is not updated to reflect its position
  209. # within the container (it will return its position with its parent
  210. # register), therefore the index should be used if the calling code
  211. # needs to work out the bit position within the container.
  212. 2 def shift_out_left
  213. 3 size.times do |i|
  214. 96 yield(bit_at_position(size - i - 1), i)
  215. end
  216. end
  217. 2 alias_method :shift_out_left_with_index, :shift_out_left
  218. # Shifts out a stream of bit objects corresponding to the size of the container. i.e. calling
  219. # this on a 32-bit container this will pass back 32 bit objects.
  220. # If there are holes then a dummy bit object will be returned that
  221. # is not writable and which will always read as 0.
  222. #
  223. # The index is also returned as a second argument. Note that
  224. # the position property of the bit is not updated to reflect its position
  225. # within the container (it will return its position with its parent
  226. # register), therefore the index should be used if the calling code
  227. # needs to work out the bit position within the container.
  228. 2 def shift_out_right
  229. 3 size.times do |i|
  230. 96 yield(bit_at_position(i), i)
  231. end
  232. end
  233. 2 alias_method :shift_out_right_with_index, :shift_out_right
  234. # Returns the bit at the given bit position if it exists, otherwise
  235. # returns an un-writable bit
  236. 2 def bit_at_position(i)
  237. 592 reg = regs.find { |reg| reg_contains_position?(reg, i) }
  238. 224 if reg
  239. 192 reg[i - bit_shift_for_reg(reg)]
  240. else
  241. 32 dummy_bit
  242. end
  243. end
  244. # @api private
  245. 2 def reg_contains_position?(reg, position)
  246. 368 start = bit_shift_for_reg(reg)
  247. 368 stop = start + reg.size - 1
  248. 368 position >= start && position <= stop
  249. end
  250. # Returns the bit at the given bit position if it exists, otherwise
  251. # returns an un-writable bit
  252. 2 def [](i)
  253. 32 bit_at_position(i)
  254. end
  255. # @api private
  256. 2 def dummy_bit
  257. 32 @dummy_bit ||= Bit.new(self, 0, writable: false)
  258. end
  259. # Call the clear_flags on all contained registers
  260. 2 def clear_flags
  261. 1 @regs.each(&:clear_flags)
  262. end
  263. end
  264. end
  265. end

lib/origen/registers/domain.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Registers
  3. 2 class Domain
  4. 2 attr_accessor :endian
  5. 2 attr_accessor :name
  6. 2 def initialize(name, options = {})
  7. options = {
  8. 104 endian: :big
  9. }.merge(options)
  10. 104 @name = name
  11. 104 @endian = options[:endian]
  12. end
  13. end
  14. end
  15. end

lib/origen/registers/msb0_delegator.rb

92.0% lines covered

25 relevant lines. 23 lines covered and 2 lines missed.
    
  1. 2 module Origen
  2. 2 module Registers
  3. 2 require 'delegate'
  4. # Thin wrapper around register objects to modify bit number interpretation
  5. #
  6. # This is provided as a convenience to make user code more readable
  7. 2 class Msb0Delegator < ::Delegator
  8. 2 def initialize(reg_object, bits)
  9. 398 @reg_object = reg_object
  10. 398 @bits = bits
  11. end
  12. 2 def __getobj__
  13. 1 @reg_object
  14. end
  15. 2 def __object__
  16. @reg_object
  17. end
  18. 2 def __setobj__(obj)
  19. 1 @reg_object = obj
  20. end
  21. 2 def inspect(options = {})
  22. 4 options[:with_bit_order] = :msb0
  23. 4 @reg_object.inspect(options)
  24. end
  25. 2 def method_missing(method, *args, &block)
  26. 6 if args.last.is_a?(Hash)
  27. args.last[:with_bit_order] = :msb0
  28. else
  29. 6 args << { with_bit_order: :msb0 }
  30. end
  31. 6 @reg_object.method_missing(method, *args, &block)
  32. end
  33. 2 def bit(*args)
  34. 11 @reg_object.bit(args, with_bit_order: :msb0)
  35. end
  36. 2 alias_method :bits, :bit
  37. 2 alias_method :[], :bit
  38. end
  39. end
  40. end

lib/origen/registers/reg.rb

87.5% lines covered

816 relevant lines. 714 lines covered and 102 lines missed.
    
  1. 2 require 'json'
  2. 2 require 'origen/registers/msb0_delegator'
  3. 2 module Origen
  4. 2 module Registers
  5. # The register class can be used to represent not only h/ware resgisters,
  6. # but really any entity which has an address and data component, such as a specific RAM location.<br>
  7. # Any registers instantiated through Origen::Registers#add_reg are instances of this class.
  8. #
  9. # All methods in BitCollection can also be called on a Reg object.
  10. 2 class Reg
  11. 2 include Origen::SubBlocks::Path
  12. 2 include Origen::SubBlocks::Domains
  13. # These attributes can be defined on a register at definition time and will get applied
  14. # to all of its contained bits unless a specific bit has its own definition of the same
  15. # attribute
  16. REG_LEVEL_ATTRIBUTES = {
  17. 2 _feature: {},
  18. _reset: { aliases: [:res] },
  19. _memory: {},
  20. _path: { aliases: [:hdl_path] },
  21. _abs_path: { aliases: [:absolute_path] },
  22. _access: {},
  23. _bit_order: {}
  24. }
  25. # Returns the object that own the register.
  26. # ==== Example
  27. # $soc.reg(:blah).owner # Returns the $soc object
  28. 2 attr_reader :owner
  29. 2 alias_method :parent, :owner
  30. # The base address of the register, this will be set dynamically
  31. # by Origen based on the parent's base address
  32. 2 attr_accessor :base_address
  33. 2 attr_writer :address # :nodoc:
  34. # Returns an integer representing the number of bits in the register
  35. 2 attr_reader :size
  36. # The register name
  37. 2 attr_accessor :name
  38. # Any feature associated with the register
  39. 2 attr_accessor :feature
  40. 2 attr_accessor :grows_backwards # :nodoc:
  41. 2 attr_accessor :lookup # :nodoc:
  42. # Returns a full path to the file in which the register was defined
  43. 2 attr_reader :define_file
  44. # Returns any application-specific meta-data attatched to the given register
  45. 2 attr_accessor :meta
  46. 2 alias_method :meta_data, :meta
  47. 2 alias_method :metadata, :meta
  48. # If the given register's reset data is backed by memory, the memory address can
  49. # be recorded in this attribute
  50. 2 attr_accessor :memory
  51. # Normally shouldn't be called directly, instantiate through add_reg
  52. # Upon initialization bits are stored as follows:
  53. # @bits -
  54. # An array of bit objects in position order, @bits[5] corresponds
  55. # to the bit as position r
  56. # @lookup -
  57. # A Hash lookup table for quickly accessing bit objects by name
  58. # @lookup = { :bit_or_bus_name => {:pos => 3, :bits => 4} }
  59. 2 def initialize(owner, address, size, name, options = {}) # :nodoc:
  60. 398 @owner = owner
  61. 398 @address = address
  62. 398 @size = size
  63. 398 @bits = []
  64. 398 @lookup = {}
  65. 398 @name = name
  66. 398 @init_as_writable = options.delete(:init_as_writable)
  67. 398 @define_file = options.delete(:define_file)
  68. 398 @from_placeholder = options.delete(:from_placeholder) || false
  69. 398 REG_LEVEL_ATTRIBUTES.each do |attribute, _meta|
  70. 2786 if @from_placeholder
  71. 1624 instance_variable_set("@#{attribute[1..-1]}", options.delete(attribute))
  72. else
  73. # If register creation is coming directly from Reg.new, instead of Placeholder,
  74. # it may not have attributes with '_' prefix
  75. 1162 instance_variable_set("@#{attribute[1..-1]}", options.delete(attribute[1..-1].to_sym))
  76. end
  77. end
  78. 398 @description_from_api = {}
  79. 398 description = options.delete(:description)
  80. 398 if description
  81. 6 @description_from_api[:_reg] = description.split(/\r?\n/)
  82. end
  83. 398 @meta = default_reg_metadata.merge(options.delete(:meta) || {})
  84. # Initialize with unwritable bits that read back as zero, can override this
  85. # to make all writable by default by setting the :init_writable option to true
  86. 398 @size.times do |n|
  87. 9758 @bits << Bit.new(self, n, writable: @init_as_writable, undefined: true)
  88. end
  89. # Internally re-map msb0 register descriptions as lsb0
  90. 445 options.each_value { |bit_desc| bit_desc[:pos] = @size - bit_desc[:pos] - bit_desc[:bits] } if bit_order == :msb0
  91. 398 add_bits_from_options(options)
  92. 398 @msb0_delegator = Msb0Delegator.new(self, @bits)
  93. end
  94. 2 def with_msb0
  95. 21 @msb0_delegator
  96. end
  97. 2 def with_lsb0
  98. 2 self
  99. end
  100. # Returns the bit order attribute of the register (either :msb0 or :lsb0). If
  101. # not explicitly defined on this register it will be inherited from the parent
  102. # and will default to :lsb0 at the top-level
  103. 2 def bit_order
  104. 472 @bit_order ||= parent.respond_to?(:bit_order) ? parent.bit_order : :lsb0
  105. end
  106. 2 def freeze
  107. 1 bits.each(&:freeze)
  108. # Call any methods which cache results to generate the instance variables
  109. # before they are frozen
  110. 1 address
  111. 1 super
  112. end
  113. 2 def bind(bitname, live_parameter)
  114. 3 unless live_parameter.respond_to?(:is_a_live_parameter?) && live_parameter.is_a_live_parameter?
  115. 1 fail 'Only live updating parameters should be bound, make sure you have not missed .live in the path to the parameter!'
  116. end
  117. 2 @parameter_bound_bits ||= {}
  118. 2 @parameter_bound_bits[bitname] = live_parameter
  119. end
  120. 2 def has_parameter_bound_bits?
  121. 26634 @parameter_bound_bits && !@parameter_bound_bits.empty?
  122. end
  123. 2 def update_bound_bits
  124. 14 @updating_bound_bits = true
  125. 14 @parameter_bound_bits.each do |name, val|
  126. 26 bits(name).write(val)
  127. end
  128. 14 @updating_bound_bits = false
  129. end
  130. 2 def updating_bound_bits?
  131. 170 @updating_bound_bits
  132. end
  133. 2 def inspect(options = {})
  134. 16 wbo = options[:with_bit_order] || :lsb0
  135. 16 domsb0 = wbo == :msb0
  136. 16 dolsb0 = !domsb0
  137. 16 if wbo != bit_order
  138. 4 Origen.log.warn "Register displayed with #{wbo} numbering, but defined with #{bit_order} numbering"
  139. 4 Origen.log.warn 'Access (and display) this register with explicit numbering like this:'
  140. 4 Origen.log.warn ''
  141. 4 Origen.log.warn " reg(:#{name}).with_msb0 # bit numbering scheme is msb0"
  142. 4 Origen.log.warn " reg(:#{name}).with_lsb0 # bit numbering scheme is lsb0 (default)"
  143. 4 Origen.log.warn " reg(:#{name}) # bit numbering scheme is lsb0 (default)"
  144. end
  145. # This fancy_output option is passed in via option hash
  146. # Even better, the output could auto-detect 7-bit vs 8-bit terminal output and adjust the parameter, but that's for another day
  147. 16 fancy_output = options[:fancy_output].nil? ? true : options[:fancy_output]
  148. 16 if fancy_output
  149. 12 horiz_double_line = '═'
  150. 12 horiz_double_tee_down = '╤'
  151. 12 horiz_double_tee_up = '╧'
  152. 12 corner_double_up_left = '╒'
  153. 12 corner_double_up_right = '╕'
  154. 12 horiz_single_line = '─'
  155. 12 horiz_single_tee_down = '┬'
  156. 12 horiz_single_tee_up = '┴'
  157. 12 horiz_single_cross = '┼'
  158. 12 horiz_double_cross = '╪'
  159. 12 corner_single_down_left = '└'
  160. 12 corner_single_down_right = '┘'
  161. 12 vert_single_line = '│'
  162. 12 vert_single_tee_left = '┤'
  163. 12 vert_single_tee_right = '├'
  164. else
  165. 4 horiz_double_line = '='
  166. 4 horiz_double_tee_down = '='
  167. 4 horiz_double_tee_up = '='
  168. 4 corner_double_up_left = '.'
  169. 4 corner_double_up_right = '.'
  170. 4 horiz_single_line = '-'
  171. 4 horiz_single_tee_down = '-'
  172. 4 horiz_single_tee_up = '-'
  173. 4 horiz_single_cross = '+'
  174. 4 horiz_double_cross = '='
  175. 4 corner_single_down_left = '`'
  176. 4 corner_single_down_right = '\''
  177. 4 vert_single_line = '|'
  178. 4 vert_single_tee_left = '<'
  179. 4 vert_single_tee_right = '>'
  180. end
  181. 16 bit_width = 13
  182. 16 desc = ["\n0x%X - :#{name}" % address]
  183. 16 r = size % 8
  184. 16 if r == 0 # || (size > 8 && domsb0)
  185. 7 desc << (' ' + corner_double_up_left + ((horiz_double_line * bit_width + horiz_double_tee_down) * 8)).chop + corner_double_up_right
  186. else
  187. 9 desc << (' ' + (' ' * (bit_width + 1) * (8 - r)) + corner_double_up_left + ((horiz_double_line * bit_width + horiz_double_tee_down) * r)).chop + corner_double_up_right
  188. end
  189. # "<#{self.class}: #{self.name}>"
  190. 16 num_bytes = (size / 8.0).ceil
  191. 16 num_bytes.times do |byte_index|
  192. # Need to add support for little endian regs here?
  193. 34 byte_number = num_bytes - byte_index
  194. 34 max_bit = (byte_number * 8) - 1
  195. 34 min_bit = max_bit - 8 + 1
  196. # BIT INDEX ROW
  197. 34 line = ' '
  198. 34 line_complete = false
  199. 34 8.times do |i|
  200. 272 bit_num = (byte_number * 8) - i - 1
  201. 272 if bit_num > size - 1
  202. 48 line << ' ' + ''.center(bit_width) unless line_complete
  203. else
  204. 224 if dolsb0
  205. 168 line << vert_single_line + "#{bit_num}".center(bit_width)
  206. else
  207. 56 line << vert_single_line + "#{size - bit_num - 1}".center(bit_width)
  208. end
  209. end
  210. end
  211. 34 line += vert_single_line unless line_complete
  212. 34 desc << line
  213. # BIT NAME ROW
  214. 34 line = ' '
  215. 34 first_done = false
  216. 34 line_complete = false
  217. 34 named_bits include_spacers: true do |name, bit, bitcounter|
  218. 140 if _bit_in_range?(bit, max_bit, min_bit)
  219. 64 if max_bit > (size - 1) && !first_done
  220. 9 (max_bit - (size - 1)).times do
  221. 48 line << ' ' * (bit_width + 1)
  222. end
  223. end
  224. 64 if bit.size > 1
  225. 36 if name
  226. 34 if bitcounter.nil?
  227. 34 bit_name = "#{name}[#{_max_bit_in_range(bit, max_bit, min_bit, options)}:#{_min_bit_in_range(bit, max_bit, min_bit, options)}]"
  228. 34 bit_span = _num_bits_in_range(bit, max_bit, min_bit)
  229. else
  230. upper = _max_bit_in_range(bit, max_bit, min_bit, options) + bitcounter - bit.size
  231. lower = _min_bit_in_range(bit, max_bit, min_bit, options) + bitcounter - bit.size
  232. if dolsb0
  233. bit_name = "#{name}[#{upper}:#{lower}]"
  234. else
  235. bit_name = "#{name}[#{upper}:#{lower}]"
  236. end
  237. bit_span = upper - lower + 1
  238. end
  239. 34 width = (bit_width * bit_span) + bit_span - 1
  240. 34 if bit_name.length > width
  241. line << vert_single_line + "#{bit_name[0..width - 2]}*"
  242. else
  243. 34 line << vert_single_line + bit_name.center(width)
  244. end
  245. else
  246. 2 bit.shift_out_left do |bit|
  247. 4 if _index_in_range?(bit.position, max_bit, min_bit)
  248. 4 line << vert_single_line + ''.center(bit_width)
  249. end
  250. end
  251. end
  252. else
  253. 28 if name
  254. 28 bit_name = "#{name}"
  255. 28 if bit_name.length > bit_width
  256. txt = "#{bit_name[0..bit_width - 2]}*"
  257. else
  258. 28 txt = bit_name
  259. end
  260. else
  261. txt = ''
  262. end
  263. 28 line << vert_single_line + txt.center(bit_width)
  264. end
  265. end
  266. 140 first_done = true
  267. end
  268. 34 line += vert_single_line
  269. 34 desc << line
  270. # BIT STATE ROW
  271. 34 line = ' '
  272. 34 first_done = false
  273. 34 named_bits include_spacers: true do |name, bit, _bitcounter|
  274. 140 if _bit_in_range?(bit, max_bit, min_bit)
  275. 64 if max_bit > (size - 1) && !first_done
  276. 9 (max_bit - (size - 1)).times do
  277. 48 line << ' ' * (bit_width + 1)
  278. end
  279. end
  280. 64 if bit.size > 1
  281. 36 if name
  282. 34 if bit.has_known_value?
  283. 34 value = '0x%X' % bit.val[_max_bit_in_range(bit, max_bit, min_bit).._min_bit_in_range(bit, max_bit, min_bit)]
  284. else
  285. if bit.reset_val == :undefined
  286. value = 'X'
  287. else
  288. value = 'M'
  289. end
  290. end
  291. 34 value += _state_desc(bit)
  292. 34 bit_span = _num_bits_in_range(bit, max_bit, min_bit)
  293. 34 width = bit_width * bit_span
  294. 34 line << vert_single_line + value.center(width + bit_span - 1)
  295. else
  296. 2 bit.shift_out_left do |bit|
  297. 4 if _index_in_range?(bit.position, max_bit, min_bit)
  298. 4 line << vert_single_line + ''.center(bit_width)
  299. end
  300. end
  301. end
  302. else
  303. 28 if name
  304. 28 if bit.has_known_value?
  305. 28 val = bit.val
  306. else
  307. if bit.reset_val == :undefined
  308. val = 'X'
  309. else
  310. val = 'M'
  311. end
  312. end
  313. 28 value = "#{val}" + _state_desc(bit)
  314. 28 line << vert_single_line + value.center(bit_width)
  315. else
  316. line << vert_single_line + ''.center(bit_width)
  317. end
  318. end
  319. end
  320. 140 first_done = true
  321. end
  322. 34 line += vert_single_line
  323. 34 desc << line
  324. 34 if size >= 8
  325. 31 r = size % 8
  326. 31 if byte_index == 0 && r != 0
  327. 6 desc << (' ' + corner_double_up_left + ((horiz_double_line * bit_width + horiz_double_tee_down) * (8 - r)).chop + horiz_double_cross + (horiz_single_line * (bit_width + 1) * r)).chop + vert_single_tee_left
  328. else
  329. 25 if byte_index == num_bytes - 1
  330. 13 desc << (' ' + corner_single_down_left + ((horiz_single_line * bit_width + horiz_single_tee_up) * 8)).chop + corner_single_down_right
  331. else
  332. 12 desc << (' ' + vert_single_tee_right + ((horiz_single_line * bit_width + horiz_single_cross) * 8)).chop + vert_single_tee_left
  333. end
  334. end
  335. else
  336. 3 desc << (' ' + (' ' * (bit_width + 1) * (8 - size)) + corner_single_down_left + ((horiz_single_line * bit_width + horiz_single_tee_up) * size)).chop + corner_single_down_right
  337. end
  338. end
  339. 16 desc.join("\n")
  340. end
  341. # Returns a hash containing all register descriptions that have been parsed so far.
  342. #
  343. # @api private
  344. 2 def description_lookup
  345. 298 @@description_lookup ||= {}
  346. end
  347. # Returns any application specific metadata that has been inherited by the
  348. # given register.
  349. # This does not account for any overridding that may have been applied to
  350. # this register specifically however, use the meta method to get that.
  351. 2 def default_reg_metadata
  352. 28826 Origen::Registers.default_reg_metadata.merge(
  353. Origen::Registers.reg_metadata[owner.class] || {})
  354. end
  355. 2 def bit_value_descriptions(bitname, options = {})
  356. options = {
  357. 24 format: :binary
  358. }.merge(options)
  359. 24 base = case options[:format]
  360. when :bin, :binary
  361. 19 2
  362. when :hex, :hexadecimal
  363. 2 16
  364. when :dec, :decimal
  365. 2 10
  366. else
  367. 1 fail "Unknown integer format: #{options[:format]}"
  368. end
  369. 23 desc = {}
  370. 23 description(bitname).each do |line|
  371. 166 if line =~ /^\s*(\d+)\s+\|\s+(.+)/
  372. 132 desc[Regexp.last_match[1].to_i(base)] = Regexp.last_match[2]
  373. end
  374. end
  375. 23 desc
  376. end
  377. # Returns the full name of the register when this has been specified in the register
  378. # description like this:
  379. #
  380. # # ** This is the Register Full Name **
  381. # # This register blah blah
  382. #
  383. # This method will also be called by bit collections to look up the name when
  384. # defined in a similar manner in the bit description.
  385. #
  386. # If no name has been specified this will return nil.
  387. 2 def full_name(bitname = :_reg, _options = {})
  388. 23 bitname, options = :_reg, bitname if bitname.is_a?(Hash)
  389. 23 desc = description(bitname).first
  390. # Capture something like this:
  391. # ** This is the full name ** - This bit blah blah
  392. 23 if desc && desc =~ /\s*\*\*\s*([^\*.]*)\s*\*\*/
  393. 14 Regexp.last_match[1].strip
  394. end
  395. end
  396. # Escapes brackets and parenthesis. Helper for description method.
  397. 2 def escape_special_char(str)
  398. 9 str.gsub('[', '\[').gsub(']', '\]').gsub('(', '\(').gsub(')', '\)') if str
  399. end
  400. # Returns the description of this register if any, if none then an empty array
  401. # will be returned
  402. #
  403. # **Note** Adding a description field will override any comment-driven documentation
  404. # of a register (ie markdown style comments)
  405. 2 def description(bitname = :_reg, options = {})
  406. 129 bitname, options = :_reg, bitname if bitname.is_a?(Hash)
  407. options = {
  408. 129 include_name: true,
  409. include_bit_values: true
  410. }.merge(options)
  411. 129 if @description_from_api[bitname]
  412. 23 desc = @description_from_api[bitname]
  413. else
  414. 106 parse_descriptions unless description_lookup[define_file]
  415. begin
  416. 106 desc = description_lookup[define_file][name][bitname] || []
  417. rescue
  418. desc = []
  419. end
  420. end
  421. 129 desc = desc.reject do |line|
  422. 348 if bitname != :_reg
  423. 263 unless options[:include_bit_values]
  424. 12 !!(line =~ /^\s*(\d+)\s+\|\s+(.+)/)
  425. end
  426. else
  427. 85 false
  428. end
  429. end
  430. 129 if desc.first
  431. 79 unless options[:include_name]
  432. 9 desc[0] = desc.first.sub(/\s*\*\*\s*#{escape_special_char(full_name(bitname))}\s*\*\*\s*-?\s*/, '')
  433. end
  434. end
  435. 9 desc.shift while desc.first && desc.first.strip.empty?
  436. 129 desc.pop while desc.last && desc.last.strip.empty?
  437. 129 desc
  438. end
  439. 2 alias_method :descriptions, :description
  440. # @api private
  441. 2 def parse_descriptions
  442. 5 desc = []
  443. 5 unless File.exist?(define_file)
  444. return desc
  445. end
  446. 5 File.readlines(define_file).each do |line|
  447. 796 if line =~ /^\s*#(.*)/
  448. 53 desc << Regexp.last_match[1].strip
  449. # http://rubular.com/r/D8lg2P5kK1 http://rubular.com/r/XP4ydPV8Fd
  450. 743 elsif line =~ /^\s*reg\(?\s*[:"'](\w+)["']?\s*,.*\sdo/ || line =~ /^\s*.*add_reg\(?\s*[:"'](\w+)["']?\s*,.*/
  451. 16 @current_reg_name = Regexp.last_match[1].to_sym
  452. 16 description_lookup[define_file] ||= {}
  453. 16 description_lookup[define_file][@current_reg_name] ||= {}
  454. 16 description_lookup[define_file][@current_reg_name][:_reg] = desc.dup
  455. 16 desc = []
  456. # http://www.rubular.com/r/7FidbC1JRA
  457. 727 elsif @current_reg_name && line =~ /^\s*(add_bit|bit|reg\.bit)s?\(?\s*\d+\.?\.?\d*\s*,\s*:(\w+)/
  458. 38 description_lookup[define_file][@current_reg_name][Regexp.last_match[2].to_sym] = desc.dup
  459. 38 desc = []
  460. else
  461. 689 desc = []
  462. end
  463. end
  464. end
  465. 2 def contains_bits?
  466. 24 true
  467. end
  468. # @api private
  469. 2 def add_bits_from_options(options = {}) # :nodoc:
  470. # options is now an array for split bit groups or a hash if single bit/range bits
  471. # Now add the requested bits to the register, removing the unwritable bits as required
  472. 398 options.each do |bit_id, bit_params|
  473. 647 if bit_params.is_a? Hash
  474. 634 description = bit_params.delete(:description)
  475. 634 if description
  476. 17 @description_from_api[bit_id] = description.split(/\r?\n/)
  477. end
  478. 634 bind(bit_id, bit_params.delete(:bind)) if bit_params[:bind]
  479. 634 position = bit_params[:pos] || 0
  480. 634 num_bits = bit_params[:bits] || 1
  481. 634 if @reset
  482. 19 if @reset.is_a?(Symbol)
  483. 8 bit_params[:res] = @reset
  484. else
  485. 11 bit_params[:res] = @reset[(num_bits + position - 1), position]
  486. end
  487. end
  488. 634 bit_params[:access] = @access if bit_params[:access].nil?
  489. 634 bit_params[:res] = bit_params[:data] if bit_params[:data]
  490. 634 bit_params[:res] = bit_params[:reset] if bit_params[:reset]
  491. 634 if num_bits == 1
  492. 240 add_bit(bit_id, position, bit_params) # and add the new one
  493. else
  494. 394 add_bus(bit_id, position, num_bits, bit_params)
  495. end
  496. 13 elsif bit_params.is_a? Array
  497. 51 description = bit_params.map { |h| h.delete(:description) }.compact.join("\n")
  498. 13 unless description.empty?
  499. @description_from_api[bit_id] = description.split(/\r?\n/)
  500. end
  501. 13 add_bus_scramble(bit_id, bit_params)
  502. end
  503. end
  504. 398 self
  505. end
  506. # This method is called whenever reg.clone is called to make a copy of a given register.
  507. # Ruby will correctly copy all instance variables but it will not drill down to copy nested
  508. # attributes, like the bits contained in @bits.
  509. # This function will therefore correctly clone all bits contained in the register also.
  510. 2 def initialize_copy(orig) # :nodoc:
  511. 106 @bits = []
  512. 106 orig.bits.each do |bit|
  513. 5054 @bits << bit.clone
  514. end
  515. 106 @lookup = orig.lookup.clone
  516. 106 self
  517. end
  518. # Returns a dummy register object that can be used on the fly, this can sometimes
  519. # be useful to configure an intricate read operation.
  520. # ==== Example
  521. # # Read bit 5 of RAM address 0xFFFF1280
  522. # dummy = Reg.dummy # Create a dummy reg to configure the read operation
  523. # dummy.address = 0xFFFF1280 # Set the address
  524. # dummy.bit(5).read!(1) # Read bit 5 expecting a 1
  525. 2 def self.dummy(size = 16)
  526. 69 Reg.new(self, 0, size, :dummy, init_as_writable: true)
  527. end
  528. # Returns each named bit collection contained in the register,
  529. 2 def named_bits(options = {})
  530. options = {
  531. 104 include_spacers: false
  532. }.merge(options)
  533. 104 result = []
  534. # test if @lookup has any values stored as an array
  535. # if so it means there is a split group of bits
  536. # process that differently to a single bit or continuous range of bits
  537. # which are typically stored in a hash
  538. 104 split_bits = false
  539. 438 @lookup.each { |_k, v| split_bits = true if v.is_a? Array }
  540. 104 if split_bits == false
  541. 100 current_pos = size
  542. # Sort by position
  543. 406 @lookup.sort_by { |_name, details| -details[:pos] }.each do |name, details|
  544. 306 pos = details[:bits] + details[:pos]
  545. 306 if options[:include_spacers] && (pos != current_pos)
  546. 22 collection = BitCollection.dummy(self, nil, size: current_pos - pos, pos: pos)
  547. 22 unless collection.size == 0
  548. 22 if block_given?
  549. 18 yield nil, collection
  550. else
  551. 4 result << [nil, collection]
  552. end
  553. end
  554. end
  555. 306 collection = BitCollection.new(self, name)
  556. 306 details[:bits].times do |i|
  557. 1524 collection << @bits[details[:pos] + i]
  558. end
  559. 306 unless collection.size == 0
  560. 306 if block_given?
  561. 243 yield name, collection
  562. else
  563. 63 result << [name, collection]
  564. end
  565. end
  566. 306 current_pos = details[:pos]
  567. end
  568. 100 if options[:include_spacers] && (current_pos != 0)
  569. 6 collection = BitCollection.dummy(self, nil, size: current_pos, pos: 0)
  570. 6 unless collection.size == 0
  571. 6 if block_given?
  572. 2 yield nil, collection
  573. else
  574. 4 result << [nil, collection]
  575. end
  576. end
  577. end
  578. 4 elsif split_bits == true # if there are split bits, need to convert all register bit values to array elements to allow sorting
  579. # if the register has bits split up across it, then store the bits in order of decreasing reg position
  580. # but first, stuff all the bits in a simple array, as single bits, or ranges of bits
  581. 4 @lookup_splits = []
  582. 4 @lookup.each do |k, v|
  583. 28 tempbit = {}
  584. 28 bitcounter = {}
  585. 28 if v.is_a? Hash
  586. # then this is already a single bit or a continuous range so just stuff it into the array
  587. 8 tempbit[k] = v
  588. 8 @lookup_splits << tempbit.clone
  589. 20 elsif v.is_a? Array
  590. # if the bitgroup is split, then decompose into single bits and continuous ranges
  591. 20 v.each_with_index do |bitdetail, _i|
  592. 56 if bitcounter.key?(k)
  593. 36 bitcounter[k] = bitcounter[k] + bitdetail[:bits]
  594. else
  595. 20 bitcounter[k] = bitdetail[:bits]
  596. end
  597. 56 tempbit[k] = bitdetail
  598. 56 @lookup_splits << tempbit.clone
  599. end
  600. end
  601. 28 if v.is_a? Array
  602. 20 @lookup_splits.each_with_index do |_e, q|
  603. 172 groupname = @lookup_splits[q].to_a[0][0]
  604. 172 if groupname == k
  605. 56 @lookup_splits[q][groupname][:bitgrouppos] = bitcounter[groupname] if groupname == k
  606. 56 bitcounter[groupname] = bitcounter[groupname] - @lookup_splits[q][groupname][:bits]
  607. end
  608. end
  609. end
  610. end
  611. # Now sort the array in descending order
  612. # Does adding the bitgrouppos need to happen after the sort ?
  613. 4 @lookup_splits = @lookup_splits.sort do |a, b|
  614. 172 b.to_a[0][1][:pos] <=> a.to_a[0][1][:pos]
  615. end
  616. 4 current_pos = size
  617. 4 countbits = {} # if countbits.method == nil
  618. 4 @master = {}
  619. 4 bitgroup = {}
  620. 4 bitinfo = {}
  621. 4 info = {}
  622. 4 @lookup_splits.each_with_index do |hash, _i|
  623. 64 name = hash.to_a[0][0]
  624. 64 details = hash.to_a[0][1]
  625. 64 bitcounter = hash.to_a[0][1][:bitgrouppos]
  626. 64 pos = details[:bits] + details[:pos]
  627. 64 if options[:include_spacers] && (pos != current_pos)
  628. collection = BitCollection.dummy(self, nil, size: current_pos - pos, pos: pos)
  629. unless collection.size == 0
  630. if block_given?
  631. yield nil, collection, bitcounter
  632. else
  633. result << [nil, collection, bitcounter]
  634. end
  635. end
  636. end
  637. 64 collection = BitCollection.new(self, name)
  638. 64 details[:bits].times do |i|
  639. 64 collection << @bits[details[:pos] + i]
  640. end
  641. 64 unless collection.size == 0
  642. 64 if block_given?
  643. 64 yield name, collection, bitcounter
  644. else
  645. result << [name, collection, bitcounter]
  646. end
  647. end
  648. 64 current_pos = details[:pos]
  649. end
  650. 4 if options[:include_spacers] && current_pos != 0
  651. collection = BitCollection.dummy(self, nil, size: current_pos, pos: 0)
  652. unless collection.size == 0
  653. if block_given?
  654. yield nil, collection, bitcounter
  655. else
  656. result << [nil, collection, bitcounter]
  657. end
  658. end
  659. end
  660. end
  661. 104 unless block_given?
  662. 19 result
  663. end
  664. end
  665. # Returns each named bit collection contained in self
  666. 2 def reverse_named_bits(_options = {})
  667. bits = []
  668. named_bits { |name, bit| bits << [name, bit] }
  669. bits.each do |bit|
  670. yield bit[0], bit[1]
  671. end
  672. end
  673. # Returns an array of occupied bit positions
  674. # ==== Example
  675. # reg :fstat, @base + 0x0000, :size => 8 do
  676. # bit 7, :ccif
  677. # bit 6, :rdcolerr
  678. # bit 5, :accerr
  679. # bit 4, :pviol
  680. # bit 0, :mgstat0
  681. # end
  682. # regs(:fstat).used_bits
  683. # => [0, 4, 5, 6, 7]
  684. #
  685. # ==== Example
  686. # reg :aguahb2, @base + 0x2A, :size => 8 do
  687. # bit 5..2, :m0b_hbstrb, :reset => 0x0
  688. # bit 1..0, :m0b_htrans, :reset => 0x2
  689. # end
  690. # regs(:aguahb2).used_bits
  691. # => [0, 1, 2, 3, 4, 5]
  692. 2 def used_bits(_options = {})
  693. 12 used_bits = []
  694. 12 named_bits do |_name, bit|
  695. 28 used_bits << bit.position if bit.size == 1
  696. 28 if bit.size > 1
  697. 18 used_bits << ((bit.position)..(bit.position + bit.size - 1)).to_a
  698. end
  699. end
  700. 12 used_bits.flatten!
  701. 12 used_bits.sort!
  702. 12 used_bits
  703. end
  704. # Returns true if any named_bits exist, false if used_bits is an empty array
  705. 2 def used_bits?(_options = {})
  706. 3 used_bits.size > 0
  707. end
  708. # Returns an array of unoccupied bit positions
  709. # ==== Example
  710. # reg :fstat, @base + 0x0000, :size => 8 do
  711. # bit 7, :ccif
  712. # bit 6, :rdcolerr
  713. # bit 5, :accerr
  714. # bit 4, :pviol
  715. # bit 0, :mgstat0
  716. # end
  717. # regs(:fstat).empty_bits
  718. # => [1, 2, 3]
  719. #
  720. # ==== Example
  721. # reg :aguahb2, @base + 0x2A, :size => 8 do
  722. # bit 5..2, :m0b_hbstrb, :reset => 0x0
  723. # bit 1..0, :m0b_htrans, :reset => 0x2
  724. # end
  725. # regs(:aguahb2).empty_bits
  726. # => [6, 7]
  727. 2 def empty_bits(_options = {})
  728. 7 array_span = (0..(size - 1)).to_a
  729. 7 empty_bits = array_span - used_bits
  730. 7 empty_bits
  731. end
  732. # Returns true if any named_bits exist, false if used_bits is an empty array
  733. 2 def empty_bits?(_options = {})
  734. 5 empty_bits.size > 0
  735. end
  736. # Proxies requests from bit collections to the register owner
  737. 2 def request(operation, options = {}) # :nodoc:
  738. 45 if operation == :read_register
  739. 29 object = reader
  740. 29 (Origen.top_level || owner).read_register_missing!(self) unless object
  741. else
  742. 16 object = writer
  743. 16 (Origen.top_level || owner).write_register_missing!(self) unless object
  744. end
  745. 45 if tester && tester.respond_to?(operation)
  746. 25 tester.send(operation, self, options) do
  747. 25 object.send(operation, self, options)
  748. end
  749. else
  750. 20 object.send(operation, self, options)
  751. end
  752. 45 self
  753. end
  754. # Returns the object that will be responsible for writing the given register
  755. 2 def writer
  756. 347 @writer ||= lookup_operation_handler(:write_register)
  757. end
  758. # Returns the object that will be responsible for reading the given register
  759. 2 def reader
  760. 107 @reader ||= lookup_operation_handler(:read_register)
  761. end
  762. # @api private
  763. 2 def lookup_operation_handler(operation)
  764. # Could have made the controller be the owner when assigned above, but may have run
  765. # into problems with the reg meta data stuff
  766. 163 reg_owner = owner.respond_to?(:controller) && owner.controller ? owner.controller : owner
  767. 163 if reg_owner.respond_to?(operation)
  768. 22 reg_owner
  769. 141 elsif reg_owner.respond_to?(:owner) && reg_owner.owner.respond_to?(operation)
  770. 25 reg_owner.owner
  771. 116 elsif Origen.top_level && Origen.top_level.respond_to?(operation)
  772. 28 Origen.top_level
  773. end
  774. end
  775. # Returns the relative address of the given register, equivalent to calling
  776. # reg.address(:relative => true)
  777. 2 def offset
  778. 13 address(relative: true)
  779. end
  780. # Returns the register address added to its current base_address value (if any).
  781. #
  782. # @param [Hash] options
  783. # @option options [Boolean] :relative (false) Return the address without adding the base address (if present)
  784. 2 def address(options = {})
  785. options = {
  786. 775 relative: false
  787. }.merge(options)
  788. 775 address = @address
  789. 775 domain_option = options[:domains] || options[:domain]
  790. 775 @domain_option ||= domain_option unless frozen?
  791. # Blow the cache when the domain option changes
  792. 775 @base_address_applied = nil unless @domain_option == domain_option
  793. 775 unless @base_address_applied
  794. # Give highest priority to the original API which allowed the object
  795. # doing register read/write to define a base_address method
  796. 93 if (writer && writer.methods.include?(:base_address) && writer.method(:base_address).arity != 0) ||
  797. (reader && reader.methods.include?(:base_address) && reader.method(:base_address).arity != 0)
  798. # This currently assumes that the base address is always the same
  799. # for reading and writing
  800. 35 if writer && writer.respond_to?(:base_address) && writer.method(:base_address).arity != 0
  801. 35 self.base_address = writer.base_address(self)
  802. elsif reader && reader.respond_to?(:base_address) && reader.method(:base_address).arity != 0
  803. self.base_address = reader.base_address(self)
  804. end
  805. else
  806. 58 o = owner.is_a?(Container) ? owner.owner : owner
  807. 58 d = domain_option || domains
  808. 58 if o && o.reg_base_address(domain: d)
  809. 58 self.base_address = o.reg_base_address(domain: d)
  810. end
  811. end
  812. 93 @base_address_applied = true
  813. end
  814. 775 unless options[:relative]
  815. 758 address += base_address if base_address
  816. end
  817. 775 if options[:address_type]
  818. Origen.deprecate 'Specifying the address_type of a register address will be removed from Origen 3'
  819. case options[:address_type]
  820. when :byte
  821. address = address * 2
  822. when :word
  823. address = address
  824. when :longword
  825. address = address / 2
  826. else
  827. fail 'Unknown address type requested!'
  828. end
  829. end
  830. 775 address
  831. end
  832. 2 alias_method :addr, :address
  833. # Returns true if the register owner matches the given name. A match will be detected
  834. # if the class names of the register's owner contains the given name.
  835. #
  836. # Alternatively if the register owner implements a method called reg_owner_alias
  837. # then the value that this returns instead will also be considered when checking if the given
  838. # name matches. This method can return an array of names if multiple aliases are required.
  839. #
  840. # Aliases can be useful for de-coupling the commonly used name, e.g. "NVM" from the actual
  841. # class name.
  842. #
  843. # @example
  844. # class C90NVM
  845. # include Origen::Registers
  846. #
  847. # def initialize
  848. # add_reg :clkdiv, 0x3, 16, :div => {:bits => 8}
  849. # end
  850. #
  851. # end
  852. #
  853. # reg = C90NVM.new.reg(:clkdiv)
  854. # reg.owned_by?(:ram) # => false
  855. # reg.owned_by?(:nvm) # => true
  856. # reg.owned_by?(:c90nvm) # => true
  857. # reg.owned_by?(:c40nvm) # => false
  858. # reg.owned_by?(:flash) # => false
  859. #
  860. # @example Using an alias
  861. # class C90NVM
  862. # include Origen::Registers
  863. #
  864. # def initialize
  865. # add_reg :clkdiv, 0x3, 16, :div => {:bits => 8}
  866. # end
  867. #
  868. # def reg_owner_alias
  869. # "flash"
  870. # end
  871. #
  872. # end
  873. #
  874. # reg = C90NVM.new.reg(:clkdiv)
  875. # reg.owned_by?(:ram) # => false
  876. # reg.owned_by?(:nvm) # => true
  877. # reg.owned_by?(:c90nvm) # => true
  878. # reg.owned_by?(:c40nvm) # => false
  879. # reg.owned_by?(:flash) # => true
  880. 2 def owned_by?(name)
  881. 40 !!(owner.class.to_s =~ /#{name}/i) || begin
  882. 30 if owner.respond_to?(:reg_owner_alias)
  883. 3 [owner.reg_owner_alias].flatten.any? do |al|
  884. 5 al.to_s =~ /#{name}/i
  885. end
  886. else
  887. 27 false
  888. end
  889. end
  890. end
  891. # Returns true if the register contains a bit(s) matching the given name
  892. # ==== Example
  893. # add_reg :control, 0x55, :status => {:pos => 1}
  894. #
  895. # reg(:control).has_bit?(:result) # => false
  896. # reg(:control).has_bit?(:status) # => true
  897. 2 def has_bit?(name)
  898. 27682 @lookup.include?(name)
  899. end
  900. 2 alias_method :has_bits?, :has_bit?
  901. 2 alias_method :has_bit, :has_bit?
  902. 2 alias_method :has_bits, :has_bit?
  903. # Add a bit to the register, should only be called internally
  904. 2 def add_bit(id, position, options = {}) # :nodoc:
  905. 240 options = { data: @bits[position].data, # If undefined preserve any data/reset value that has
  906. res: @bits[position].data, # already been applied at reg level
  907. }.merge(options)
  908. 240 @lookup[id] = { pos: position, bits: 1, feature: options[:feature] }
  909. 240 @bits.delete_at(position) # Remove the initial bit from this position
  910. 240 @bits.insert(position, Bit.new(self, position, options))
  911. 240 self
  912. end
  913. # Add a bus to the register, should only be called internally
  914. 2 def add_bus(id, position, size, options = {}) # :nodoc:
  915. 394 default_data = 0
  916. 394 size.times do |n|
  917. 5608 default_data |= @bits[position + n].data << n
  918. end
  919. 394 options = { data: default_data, # If undefined preserve any data/reset value that has
  920. res: default_data, # already been applied at reg level
  921. }.merge(options)
  922. 394 @lookup[id] = { pos: position, bits: size }
  923. 394 size.times do |n|
  924. 5608 bit_options = options.dup
  925. 5608 bit_options[:data] = options[:data][n]
  926. 5608 if options[:res].is_a?(Symbol)
  927. 16 bit_options[:res] = options[:res]
  928. else
  929. 5592 bit_options[:res] = options[:res][n]
  930. end
  931. 5608 @bits.delete_at(position + n)
  932. 5608 @bits.insert(position + n, Bit.new(self, position + n, bit_options))
  933. end
  934. 394 self
  935. end
  936. 2 def add_bus_scramble(id, array_of_hashes = [])
  937. 13 array_of_hashes.each do |options|
  938. 38 bind(id, options.delete(:bind)) if options[:bind]
  939. 38 position = options[:pos] || 0
  940. 38 num_bits = options[:bits] || 1
  941. 38 size = options[:bits]
  942. 38 options[:data] = options[:data] if options[:data]
  943. 38 options[:res] = options[:reset] if options[:reset]
  944. 38 default_data = 0
  945. 38 size.times do |n|
  946. 39 default_data |= @bits[position + n].data << n
  947. end
  948. 38 options = { data: default_data, # If undefined preserve any data/reset value that has
  949. res: default_data, # already been applied at reg level
  950. }.merge(options)
  951. 38 @lookup[id] = [] if @lookup[id].nil?
  952. 38 @lookup[id] = @lookup[id].push(pos: position, bits: size)
  953. 38 size.times do |n|
  954. 39 bit_options = options.dup
  955. 39 bit_options[:data] = options[:data][n]
  956. 39 bit_options[:res] = options[:res][n]
  957. 39 @bits.delete_at(position + n)
  958. 39 @bits.insert(position + n, Bit.new(self, position + n, bit_options))
  959. end
  960. 38 self
  961. end
  962. end
  963. # Delete the bits in the collection from the register
  964. 2 def delete_bit(collection)
  965. 2 [collection.name].flatten.each do |name|
  966. 2 @lookup.delete(name)
  967. end
  968. 2 collection.each do |bit|
  969. 16 @bits.delete_at(bit.position) # Remove the bit
  970. 16 @bits.insert(bit.position, Bit.new(self, bit.position, writable: @init_as_writable))
  971. end
  972. 2 self
  973. end
  974. 2 alias_method :delete_bits, :delete_bit
  975. # @api private
  976. 2 def expand_range(range, wbo = :lsb0)
  977. 44 if range.first > range.last
  978. 34 range = Range.new(range.last, range.first)
  979. end
  980. 44 range = range.to_a
  981. 44 range.reverse! if wbo == :msb0
  982. 44 range.each do |i|
  983. 566 yield i
  984. end
  985. end
  986. # Returns the bit object(s) responding to the given name, wrapped in a BitCollection.
  987. # This method also accepts multiple name possibilities, if neither bit exists in
  988. # the register it will raise an error, otherwise it will return the first match.
  989. # If no args passed in, it will return a BitCollection containing all bits.
  990. # If a number is passed in then the bits from those positions are returned.
  991. # ==== Example
  992. # add_reg :control, 0x55, :status => {:pos => 1, :bits => 2},
  993. # :fail => {:pos => 0}
  994. #
  995. # reg(:control).bit(:fail) # => Returns a BitCollection containing the fail bit
  996. # reg(:control).bits(:status) # => Returns a BifCollection containing the status bits
  997. # reg(:control).bit(:bist_fail, :fail) # => Returns a BitCollection containing the fail bit
  998. # reg(:control).bit(0) # => Returns a BitCollection containing the fail bit
  999. # reg(:control).bit(1) # => Returns a BitCollection containing status bit
  1000. # reg(:control).bit(1,2) # => Returns a BitCollection containing both status bits
  1001. 2 def bit(*args)
  1002. # allow msb0 bit numbering if requested
  1003. 16642 wbo = :lsb0
  1004. 16642 if args.last.is_a?(Hash)
  1005. 96 wbo = args.last.delete(:with_bit_order) || :lsb0
  1006. 96 args.pop if args.last.size == 0
  1007. end
  1008. 16642 multi_bit_names = false
  1009. # return get_bits_with_constraint(nil,:default) if args.size == 0
  1010. 16642 constraint = extract_feature_params(args)
  1011. 16642 if constraint.nil?
  1012. 16629 constraint = :default
  1013. end
  1014. 16642 collection = BitCollection.new(self, :unknown, [], with_bit_order: wbo)
  1015. 16642 if args.size == 0
  1016. 156 collection.add_name(name)
  1017. 156 @bits.each do |bit|
  1018. 6050 collection << get_bits_with_constraint(bit.position, constraint)
  1019. end
  1020. else
  1021. 16486 args.flatten!
  1022. 16486 args.sort!
  1023. 16486 args.reverse! if wbo == :msb0
  1024. 16486 args.each do |arg_item|
  1025. 16527 if arg_item.is_a?(Integer)
  1026. 15954 b = get_bits_with_constraint(arg_item, constraint, with_bit_order: wbo)
  1027. 15954 collection << b if b
  1028. 573 elsif arg_item.is_a?(Range)
  1029. 44 expand_range(arg_item, wbo) do |bit_number|
  1030. 566 collection << get_bits_with_constraint(bit_number, constraint, with_bit_order: wbo)
  1031. end
  1032. else
  1033. 529 multi_bit_names = args.size > 1
  1034. # Reaches here if bit name is specified
  1035. 529 if @lookup.include?(arg_item)
  1036. 524 split_bits = false
  1037. 3857 @lookup.each { |_k, v| split_bits = true if v.is_a? Array }
  1038. 524 coll = get_lookup_feature_bits(arg_item, constraint, split_bits)
  1039. 524 if coll
  1040. 524 coll.each do |b|
  1041. 4436 collection.add_name(arg_item)
  1042. 4436 collection << b
  1043. end
  1044. end
  1045. end
  1046. end
  1047. end
  1048. end
  1049. 16642 if collection.size == 0
  1050. # Originally Origen returned nil when asking for a bit via an index which does not
  1051. # exist, e.g. reg[1000] => nil
  1052. # The args numeric clause here is to maintain that behavior
  1053. 2 if Origen.config.strict_errors && !args.all? { |arg| arg.is_a?(Numeric) }
  1054. puts "Register #{@name} does not have a bits(s) named :#{args.join(', :')} or it might not be enabled."
  1055. puts 'This could also be a typo, these are the valid bit names:'
  1056. puts @lookup.keys
  1057. fail 'Missing bits error!'
  1058. end
  1059. nil
  1060. else
  1061. 16641 if multi_bit_names
  1062. 5 collection.sort_by!(&:position)
  1063. 5 wbo == :msb0 ? collection.with_msb0 : collection.with_lsb0
  1064. end
  1065. 16641 wbo == :msb0 ? collection.with_msb0 : collection.with_lsb0
  1066. end
  1067. end
  1068. 2 alias_method :bits, :bit
  1069. 2 alias_method :[], :bit
  1070. 2 def get_bits_with_constraint(number, params, options = {})
  1071. 22570 options = { with_bit_order: :lsb0 }.merge(options)
  1072. # remap to lsb0 number to grab correct bit
  1073. 22570 if options[:with_bit_order] == :msb0
  1074. 244 number = size - number - 1
  1075. end
  1076. 22570 return nil unless @bits[number]
  1077. 22569 if (params == :default || !params) && @bits[number].enabled?
  1078. 22329 @bits[number]
  1079. 240 elsif params == :none && !@bits[number].has_feature_constraint?
  1080. 32 @bits[number]
  1081. 208 elsif params == :all
  1082. 48 @bits[number]
  1083. 160 elsif params.class == Array
  1084. 32 params.each do |param|
  1085. 32 unless @bits[number].enabled_by_feature?(param)
  1086. 32 return nil
  1087. end
  1088. @bits[number]
  1089. end
  1090. 128 elsif @bits[number].enabled_by_feature?(params)
  1091. 16 @bits[number]
  1092. else
  1093. 112 return Bit.new(self, number, writable: false)
  1094. end
  1095. end
  1096. 2 def get_lookup_feature_bits(bit_name, params, split_group_reg)
  1097. ##
  1098. 524 if split_group_reg == false # if this register has single bits and continuous ranges
  1099. 490 if @lookup.include?(bit_name)
  1100. 490 collection = BitCollection.new(self, bit_name)
  1101. 490 (@lookup[bit_name][:bits]).times do |i|
  1102. 4334 collection << @bits[@lookup[bit_name][:pos] + i]
  1103. end
  1104. 490 if !params || params == :default
  1105. 487 if collection.enabled?
  1106. 485 return collection
  1107. end
  1108. 3 elsif params == :none
  1109. unless collection.has_feature_constraint?
  1110. return collection
  1111. end
  1112. 3 elsif params == :all
  1113. 1 return collection
  1114. 2 elsif params.class == Array
  1115. 2 if params.all? { |param| collection.enabled_by_feature?(param) }
  1116. return collection
  1117. end
  1118. else
  1119. 1 if collection.enabled_by_feature?(params)
  1120. 1 return collection
  1121. end
  1122. end
  1123. 3 return BitCollection.dummy(self, bit_name, size: collection.size, pos: @lookup[bit_name][:pos])
  1124. else
  1125. return []
  1126. end
  1127. 34 elsif split_group_reg == true # if this registers has split bits in its range
  1128. 34 if @lookup.is_a?(Hash) # && @lookup.include?(bit_name)
  1129. 34 collection = false
  1130. 34 @lookup.each do |k, v| # k is the bitname, v is the hash of bit data
  1131. 190 if k == bit_name
  1132. 34 collection ||= BitCollection.new(self, k)
  1133. 34 if v.is_a?(Array)
  1134. 28 v.reverse_each do |pb| # loop each piece of bit group data
  1135. 90 (pb[:bits]).times do |i|
  1136. 96 collection << @bits[pb[:pos] + i]
  1137. end
  1138. end
  1139. else
  1140. 6 (v[:bits]).times do |i|
  1141. 6 collection << @bits[v[:pos] + i]
  1142. end
  1143. end
  1144. end
  1145. end
  1146. 34 if !params || params == :default
  1147. 34 if collection.enabled?
  1148. 34 return collection
  1149. end
  1150. elsif params == :none
  1151. unless collection.has_feature_constraint?
  1152. return collection
  1153. end
  1154. elsif params == :all
  1155. return collection
  1156. elsif params.class == Array
  1157. if params.all? { |param| collection.enabled_by_feature?(param) }
  1158. return collection
  1159. end
  1160. else
  1161. if collection.enabled_by_feature?(params)
  1162. return collection
  1163. end
  1164. end
  1165. if @lookup.is_a?(Hash) && @lookup[bit_name].is_a?(Array)
  1166. return BitCollection.dummy(self, bit_name, size: collection.size, pos: @lookup[bit_name][0][:pos])
  1167. else
  1168. return BitCollection.dummy(self, bit_name, size: collection.size, pos: @lookup[bit_name[:pos]])
  1169. end
  1170. else
  1171. return []
  1172. end
  1173. end
  1174. end
  1175. 2 def extract_feature_params(args)
  1176. 33179 index = args.find_index { |arg| arg.class == Hash }
  1177. 16642 if index
  1178. 13 params = args.delete_at(index)
  1179. else
  1180. 16629 params = nil
  1181. end
  1182. 16642 if params
  1183. 13 return params[:enabled_features] || params[:enabled_feature]
  1184. else
  1185. return nil
  1186. end
  1187. end
  1188. # All other Reg methods are delegated to BitCollection
  1189. 2 def method_missing(method, *args, &block) # :nodoc:
  1190. 816 wbo = :lsb0
  1191. 816 if args.last.is_a?(Hash)
  1192. 9 wbo = args.last[:with_bit_order] if args.last.key?(:with_bit_order)
  1193. end
  1194. 816 if method.to_sym == :to_ary || method.to_sym == :to_hash
  1195. nil
  1196. 814 elsif meta_data_method?(method)
  1197. 13 extract_meta_data(method, *args)
  1198. else
  1199. 801 if BitCollection.instance_methods.include?(method)
  1200. 729 to_bit_collection(with_bit_order: wbo).send(method, *args, &block)
  1201. 72 elsif has_bits?(method)
  1202. 72 bits(method, with_bit_order: wbo)
  1203. else
  1204. super
  1205. end
  1206. end
  1207. end
  1208. 2 def to_bit_collection(options = {})
  1209. 729 BitCollection.new(self, name, @bits, options)
  1210. end
  1211. # Recognize that Reg responds to all BitCollection methods methods based on
  1212. # application-specific meta data properties
  1213. 2 def respond_to?(*args) # :nodoc:
  1214. 27610 sym = args.first.to_sym
  1215. 27610 meta_data_method?(sym) || has_bits?(sym) || super(sym) || BitCollection.instance_methods.include?(sym)
  1216. end
  1217. # Copy overlays from one reg object to another
  1218. # ==== Example
  1219. # reg(:data_copy).has_overlay? # => false
  1220. # reg(:data).overlay("data_val")
  1221. # reg(:data_copy).copy_overlays_from(reg(:data))
  1222. # reg(:data_copy).has_overlay? # => true
  1223. 2 def copy_overlays_from(reg, options = {})
  1224. size.times do |i|
  1225. source_bit = reg.bit[i]
  1226. if source_bit.has_overlay?
  1227. ov = source_bit.overlay_str
  1228. # If an id has been supplied make sure any trailing ID in the source is
  1229. # changed to supplied identifier
  1230. ov.gsub!(/_\d$/, "_#{options[:update_id]}") if options[:update_id]
  1231. @bits[i].overlay(ov)
  1232. end
  1233. end
  1234. self
  1235. end
  1236. # Copies data from one reg object to another
  1237. # ==== Example
  1238. # reg(:data_copy).data # => 0
  1239. # reg(:data).write(0x1234)
  1240. # reg(:data_copy).copy_data_from(reg(:data))
  1241. # reg(:data_copy).data # => 0x1234
  1242. 2 def copy_data_from(reg)
  1243. size.times do |i|
  1244. @bits[i].write(reg.bit[i].data)
  1245. end
  1246. self
  1247. end
  1248. # Copies data and overlays from one reg object to another, it does not copy
  1249. # read or store flags
  1250. 2 def copy(reg)
  1251. 3 size.times do |i|
  1252. 48 source_bit = reg.bit[i]
  1253. 48 @bits[i].overlay(source_bit.overlay_str) if source_bit.has_overlay?
  1254. 48 @bits[i].write(source_bit.data)
  1255. end
  1256. 3 self
  1257. end
  1258. # Returns the BITWISE AND of reg with another reg or a number, the state of
  1259. # both registers remains unchanged
  1260. # ==== Example
  1261. # reg(:data).write(0x5555)
  1262. # reg(:data2).write(0xFFFF)
  1263. # reg(:data) & 0xFF00 # => 0x5500
  1264. # reg(:data) & reg(:data2) # => 0x5555
  1265. 2 def &(val)
  1266. data & Reg.clean_value(val)
  1267. end
  1268. # Returns the BITWISE OR of reg with another reg or a number, the state of
  1269. # both registers remains unchanged
  1270. 2 def |(val)
  1271. data | Reg.clean_value(val)
  1272. end
  1273. # Returns the SUM of reg with another reg or a number, the state of
  1274. # both registers remains unchanged
  1275. 2 def +(val)
  1276. data + Reg.clean_value(val)
  1277. end
  1278. # Returns the SUBTRACTION of reg with another reg or a number, the state of
  1279. # both registers remains unchanged
  1280. 2 def -(val)
  1281. data - Reg.clean_value(val)
  1282. end
  1283. # Returns the DIVISION of reg with another reg or a number, the state of
  1284. # both registers remains unchanged
  1285. 2 def /(val)
  1286. data / Reg.clean_value(val)
  1287. end
  1288. # Returns the PRODUCT of reg with another reg or a number, the state of
  1289. # both registers remains unchanged
  1290. 2 def *(val)
  1291. data * Reg.clean_value(val)
  1292. end
  1293. # Cleans an input value, in some cases it could be a register object, or an explicit value.
  1294. # This will return an explicit value in either case.
  1295. 2 def self.clean_value(value) # :nodoc:
  1296. 30 value = value.val if value.respond_to?('val') # Pull out the data value if a reg object has been passed in
  1297. 30 value
  1298. end
  1299. # @api private
  1300. 2 def meta_data_method?(method)
  1301. 28424 attr_name = method.to_s.gsub(/\??=?/, '').to_sym
  1302. 28424 if default_reg_metadata.key?(attr_name)
  1303. 18 if method.to_s =~ /\?/
  1304. 4 [true, false].include?(default_reg_metadata[attr_name])
  1305. else
  1306. 14 true
  1307. end
  1308. else
  1309. 28406 false
  1310. end
  1311. end
  1312. 2 def extract_meta_data(method, *args)
  1313. 13 method = method.to_s.sub('?', '')
  1314. 13 if method =~ /=/
  1315. 1 instance_variable_set("@#{method.sub('=', '')}", args.first)
  1316. else
  1317. 12 instance_variable_get("@#{method}") || meta[method.to_sym]
  1318. end
  1319. end
  1320. # Returns true if the register is constrained by the given/any feature
  1321. 2 def enabled_by_feature?(name = nil)
  1322. 124 if !name
  1323. 52 !!feature
  1324. else
  1325. 72 if feature.class == Array
  1326. 20 feature.each do |f|
  1327. 28 if f == name
  1328. 17 return true
  1329. end
  1330. end
  1331. 3 return false
  1332. else
  1333. 52 return feature == name
  1334. end
  1335. end
  1336. end
  1337. 2 alias_method :has_feature_constraint?, :enabled_by_feature?
  1338. # Query the owner heirarchy to see if this register is enabled
  1339. 2 def enabled?
  1340. 5794 if feature
  1341. 137 value = false
  1342. 137 current_owner = self
  1343. 137 if feature.class == Array
  1344. 33 feature.each do |f|
  1345. 33 current_owner = self
  1346. 33 loop do
  1347. 99 if current_owner.respond_to?(:owner)
  1348. 66 current_owner = current_owner.owner
  1349. 66 if current_owner.respond_to?(:has_feature?)
  1350. 33 if current_owner.has_feature?(f)
  1351. value = true
  1352. break
  1353. end
  1354. end
  1355. else # if current owner does not have a owner
  1356. 33 value = false
  1357. 33 break
  1358. end
  1359. end # loop end
  1360. 33 unless value
  1361. 33 if Origen.top_level && \
  1362. Origen.top_level.respond_to?(:has_feature?) && \
  1363. Origen.top_level.has_feature?(f)
  1364. value = true
  1365. unless value
  1366. break
  1367. end
  1368. end
  1369. end
  1370. 33 unless value
  1371. 33 break # break if feature not found and return false
  1372. end
  1373. end # iterated through all features in array
  1374. 33 return value
  1375. else # if feature.class != Array
  1376. 104 loop do
  1377. 174 if current_owner.respond_to?(:owner)
  1378. 139 current_owner = current_owner.owner
  1379. 139 if current_owner.respond_to?(:has_feature?)
  1380. 104 if current_owner.has_feature?(feature)
  1381. 69 value = true
  1382. 69 break
  1383. end
  1384. end
  1385. else # if current owner does not have a owner
  1386. 35 value = false
  1387. 35 break
  1388. end
  1389. end # loop end
  1390. 104 unless value
  1391. 35 if Origen.top_level && \
  1392. Origen.top_level.respond_to?(:has_feature?) && \
  1393. Origen.top_level.has_feature?(feature)
  1394. value = true
  1395. end
  1396. end
  1397. 104 return value
  1398. end
  1399. else
  1400. 5657 return true
  1401. end
  1402. end
  1403. # Returns true if any of the bits within this register has feature
  1404. # associated with it.
  1405. 2 def has_bits_enabled_by_feature?(name = nil)
  1406. if !name
  1407. bits.any?(&:has_feature_constraint?)
  1408. else
  1409. bits.any? { |bit| bit.enabled_by_feature?(name) }
  1410. end
  1411. end
  1412. 2 def to_json(*args)
  1413. 3 JSON.pretty_generate({
  1414. name: name,
  1415. full_name: full_name,
  1416. address: address,
  1417. offset: offset,
  1418. size: size,
  1419. path: path,
  1420. reset_value: reset_value,
  1421. description: description(include_name: false, include_bit_values: false),
  1422. bits: named_bits.map do |name, bit|
  1423. {
  1424. 7 name: name,
  1425. full_name: bit.full_name,
  1426. position: bit.position,
  1427. size: bit.size,
  1428. reset_value: bit.reset_value,
  1429. access: bit.access,
  1430. description: bit.description(include_name: false, include_bit_values: false),
  1431. bit_values: bit.bit_value_descriptions.map do |val, desc|
  1432. {
  1433. 2 value: val,
  1434. description: desc
  1435. }
  1436. end
  1437. }
  1438. end
  1439. }, *args)
  1440. end
  1441. 2 private
  1442. 2 def _state_desc(bits)
  1443. 62 state = []
  1444. 62 unless bits.readable? && bits.writable?
  1445. if bits.readable?
  1446. state << 'RO'
  1447. else
  1448. state << 'WO'
  1449. end
  1450. end
  1451. 62 state << 'Rd' if bits.is_to_be_read?
  1452. 62 state << 'Str' if bits.is_to_be_stored?
  1453. 62 state << 'Ov' if bits.has_overlay?
  1454. 62 if state.empty?
  1455. 62 ''
  1456. else
  1457. "(#{state.join('|')})"
  1458. end
  1459. end
  1460. 2 def _max_bit_in_range(bits, max, _min, options = { with_bit_order: false })
  1461. 68 upper = bits.position + bits.size - 1
  1462. 68 if options[:with_bit_order] == :msb0
  1463. 10 bits.size - ([upper, max].min - bits.position) - 1
  1464. else
  1465. 58 [upper, max].min - bits.position
  1466. end
  1467. end
  1468. 2 def _min_bit_in_range(bits, _max, min, options = { with_bit_order: false })
  1469. 68 lower = bits.position
  1470. 68 if options[:with_bit_order] == :msb0
  1471. 10 bits.size - ([lower, min].max - lower) - 1
  1472. else
  1473. 58 [lower, min].max - bits.position
  1474. end
  1475. end
  1476. # Returns true if some portion of the given bits falls
  1477. # within the given range
  1478. 2 def _bit_in_range?(bits, max, min)
  1479. 280 upper = bits.position + bits.size - 1
  1480. 280 lower = bits.position
  1481. 280 !((lower > max) || (upper < min))
  1482. end
  1483. # Returns the number of bits from the given bits that
  1484. # fall within the given range
  1485. 2 def _num_bits_in_range(bits, max, min)
  1486. 68 upper = bits.position + bits.size - 1
  1487. 68 lower = bits.position
  1488. 68 [upper, max].min - [lower, min].max + 1
  1489. end
  1490. # Returns true if the given number is is the
  1491. # given range
  1492. 2 def _index_in_range?(i, max, min)
  1493. 8 !((i > max) || (i < min))
  1494. end
  1495. 2 def _bit_rw(bits)
  1496. if bits.readable? && bits.writable?
  1497. 'RW'
  1498. elsif bits.readable?
  1499. 'RO'
  1500. elsif bits.writable?
  1501. 'WO'
  1502. else
  1503. 'X'
  1504. end
  1505. end
  1506. end
  1507. end
  1508. end

lib/origen/registers/reg_collection.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Registers
  3. # This is a regular Ruby hash that is used to store collections of Reg objects, it has additional
  4. # methods added to allow interaction with the contained registers.
  5. # All Ruby hash methods are also available - http://www.ruby-doc.org/core/classes/Hash.html
  6. 2 class RegCollection < Hash
  7. # Returns the object that owns the registers
  8. 2 attr_reader :owner
  9. 2 def initialize(owner, _options = {})
  10. 2730 @owner = owner
  11. end
  12. 2 def inspect
  13. 15 map { |k, _v| k }.inspect
  14. end
  15. # Display all regs visually in a console session
  16. 2 def show
  17. 2 puts map { |_k, v| v }.inspect
  18. end
  19. end
  20. end
  21. end

lib/origen/revision_control.rb

66.67% lines covered

15 relevant lines. 10 lines covered and 5 lines missed.
    
  1. 2 module Origen
  2. 2 module RevisionControl
  3. 2 require 'origen/revision_control/base'
  4. 2 autoload :DesignSync, 'origen/revision_control/design_sync'
  5. 2 autoload :Git, 'origen/revision_control/git'
  6. 2 autoload :Subversion, 'origen/revision_control/subversion'
  7. 2 autoload :Perforce, 'origen/revision_control/perforce'
  8. IGNORE_DIRS = %w(
  9. 2 .ws .lsf log output web coverage .ref .yardoc .collection .bin
  10. .session .bundle .tpc pkg tmp .git
  11. )
  12. IGNORE_FILES = %w(
  13. 2 target/.default release_note.txt *.swp *.swo *~ .bin
  14. list/referenced.list tags .ref .pdm/pi_attributes.txt
  15. environment/.default
  16. )
  17. # Creates a new revision controller object based on the supplied :local and :remote
  18. # options.
  19. #
  20. # The revision control system will be worked out from the supplied remote value. This method
  21. # should therefore be used whenever the remote is a variable that could refer to many different
  22. # systems.
  23. #
  24. # @example
  25. #
  26. # # I know that the remote refers to DesignSync
  27. # rc = Origen::RevisionControl::DesignSync.new remote: "sync//....", local: "my/path"
  28. #
  29. # # The remote is a variable and I don't know the type
  30. # rc = Origen::RevisionControl.new remote: rc_url, local: "my/path"
  31. 2 def self.new(options = {})
  32. case
  33. when options[:remote] =~ /^sync/
  34. DesignSync.new(options)
  35. when options[:remote] =~ /git/
  36. Git.new(options)
  37. when options[:remote] =~ /^p4/
  38. Perforce.new(options)
  39. else
  40. fail "Could not work out the revision control system for: #{options[:remote]}"
  41. end
  42. end
  43. end
  44. end

lib/origen/revision_control/base.rb

58.82% lines covered

68 relevant lines. 40 lines covered and 28 lines missed.
    
  1. 2 require 'open3'
  2. 2 module Origen
  3. 2 module RevisionControl
  4. # Base class of all revision control system drivers,
  5. # all drivers should support the API methods defined here.
  6. #
  7. # Each instance of this class represents the concept of mapping a local directory to
  8. # a remote repository.
  9. #
  10. # Origen.app.rc will return an instance of this class for the revision control system used
  11. # by the current application, the :local attribute will be automatically set to
  12. # Origen.root and the :remote attribute will be set per the revision control attributes
  13. # defined in config/application.rb.
  14. 2 class Base
  15. # Returns a pointer to the remote location (a Pathname object)
  16. 2 attr_reader :remote
  17. # Returns a pointer to the local location (a Pathname object)
  18. 2 attr_reader :local
  19. # Method to use by Origen::RemoteManager to handle fetching a remote file
  20. 2 attr_reader :remotes_method
  21. # rubocop:disable Lint/UnusedMethodArgument
  22. # All revision control instances represent a remote server mapping
  23. # to a local directory, :remote and :local options are required
  24. 2 def initialize(options = {})
  25. 2 unless options[:remote] && options[:local]
  26. fail ':remote and :local options must be supplied when instantiating a new RevisionControl object'
  27. end
  28. 2 @remote = Pathname.new(options[:remote])
  29. 2 @local = Pathname.new(options[:local]).expand_path
  30. 2 @remotes_method = :checkout
  31. 2 initialize_local_dir(options)
  32. end
  33. # Build the local workspace for the first time.
  34. #
  35. # This is roughly equivalent to running the checkout command, but should be used in the case where
  36. # the local workspace is being setup for the first time.
  37. 2 def build(options = {})
  38. fail "The #{self.class} driver does not support the build method!"
  39. end
  40. # Checkout the given file or directory, it returns a path to the local file.
  41. #
  42. # The path argument is optional and when not supplied the entire directory will be checked out.
  43. #
  44. # @param [String, Pathname] path
  45. # The path to the remote item to checkout, this can be a pointer to a file or directory
  46. # and it can either be a relative path or absolute path to either the local or remote locations.
  47. # Multiple values can be supplied and should be separated by a space.
  48. # @param [Hash] options Options to customize the operation
  49. # @option options [Boolean] :force (false) Force overwrite of any existing local copy
  50. # @option options [String] :version (nil) A specific version to checkout, will get latest if
  51. # not supplied
  52. # @option options [Boolean] :verbose (true) When true will show the command being executed and
  53. # the raw output from the underlying revision control tool. When false will show nothing, but
  54. # will still raise an error if the underlying command fails.
  55. 2 def checkout(path = nil, options = {})
  56. fail "The #{self.class} driver does not support the checkout method!"
  57. end
  58. # Checkin the given file or directory, it returns a path to the local file.
  59. #
  60. # The path argument is optional and when not supplied the entire directory will be checked in.
  61. #
  62. # @param [String, Pathname] path
  63. # The path to the remote item to checkout, this can be a pointer to a file or directory
  64. # and it can either be a relative path or absolute path to either the local or remote locations.
  65. # Multiple values can be supplied and should be separated by a space.
  66. # @param [Hash] options Options to customize the operation
  67. # @option options [Boolean] :force (false) Force overwrite of any newer version of the file that may
  68. # exist, i.e. force checkin the current version to become the latest.
  69. # @option options [Boolean] :unmanaged (false) Include files matching the given path that are not currently
  70. # managed by the revision control system.
  71. # @option options [Boolean] :comment (nil) Optionally supply a checkin comment.
  72. # @option options [Boolean] :verbose (true) When true will show the command being executed and
  73. # the raw output from the underlying revision control tool. When false will show nothing, but
  74. # will still raise an error if the underlying command fails.
  75. 2 def checkin(path = nil, options = {})
  76. fail "The #{self.class} driver does not support the checkin method!"
  77. end
  78. # Returns a hash containing the list of files that have changes compared to the given tag
  79. # or compared to the latest version (on the server).
  80. #
  81. # {
  82. # :added => [], # Paths to files that have been added since the previous tag
  83. # :removed => [], # Paths to files that have been removed since the previous tag
  84. # :changed => [], # Paths to files that have changed since the previous tag
  85. # :present => true/false, # Convenience attribute for the caller to check if there are any changes, when
  86. # # true at least one of the other arrays will contain a value
  87. # }
  88. #
  89. # The dir argument is optional and when not supplied the entire directory will be checked for
  90. # changes.
  91. #
  92. # Note that added files only refers to those files which have been checked into revision control
  93. # since the compared to version, it does not refer to unmanaged files in the workspace.
  94. # Use the unmanaged method to get a list of those.
  95. #
  96. # Note also that while a file is considered added or removed depends on the chronological
  97. # relationship between the current version (the user's workspace) and the reference version.
  98. # If the reference version is older than the current version (i.e. an earlier tag), then an added
  99. # file means a file that the current version has and the reference (previous) version did not have.
  100. #
  101. # However if the reference version is newer than the current version (e.g. when comparing to a newer
  102. # tag or the latest version on the server), then an added file means a file that the current
  103. # version does not have and which has been added in a newer version of the remote directory.
  104. #
  105. # @param [String, Pathname] dir
  106. # The path to a sub-directory to check for changes, it can either be a relative path or an
  107. # absolute path to either the local or remote locations.
  108. # @param [Hash] options Options to customize the operation
  109. # @option options [String] :version (nil) A specific version to compare against, will compare to
  110. # latest if not supplied
  111. # @option options [Boolean] :verbose (false) When true will show the command being executed and
  112. # the raw output from the underlying revision control tool. When false will show nothing. False
  113. # is the default as with this command the user is more concerned with seeing the organized
  114. # summary that is returned from this method.
  115. 2 def changes(dir = nil, options = {})
  116. fail "The #{self.class} driver does not support the changes method!"
  117. end
  118. # Returns an array containing the files that have un-committed local changes.
  119. #
  120. # The dir argument is optional and when not supplied the entire directory will be checked for
  121. # changes.
  122. #
  123. # @param [String, Pathname] dir
  124. # The path to a sub-directory to check for changes, it can either be a relative path or an
  125. # absolute path to either the local or remote locations.
  126. # @param [Hash] options Options to customize the operation
  127. # @option options [Boolean] :verbose (false) When true will show the command being executed and
  128. # the raw output from the underlying revision control tool. When false will show nothing. False
  129. # is the default as with this command the user is more concerned with seeing the organized
  130. # summary that is returned from this method.
  131. 2 def local_modifications(dir = nil, options = {})
  132. fail "The #{self.class} driver does not support the local_modifications method!"
  133. end
  134. # Returns an array containing the list of files that are present in the given directory but
  135. # which are not managed by the revision control system.
  136. #
  137. # The dir argument is optional and when not supplied the entire directory will be checked for
  138. # unmanaged files.
  139. #
  140. # @param [String, Pathname] dir
  141. # The path to a sub-directory to check for unmanaged files, it can either be a relative path or an
  142. # absolute path to either the local or remote locations.
  143. # @param [Hash] options Options to customize the operation
  144. # @option options [Boolean] :verbose (false) When true will show the command being executed and
  145. # the raw output from the underlying revision control tool. When false will show nothing. False
  146. # is the default as with this command the user is more concerned with seeing the organized
  147. # summary that is returned from this method.
  148. 2 def unmanaged(dir = nil, options = {})
  149. fail "The #{self.class} driver does not support the unmanaged method!"
  150. end
  151. # Returns the command the user must run to execute a diff of the current version of the given file
  152. # against the given version of it.
  153. #
  154. # @param [String, Pathname] file
  155. # The local path to the file to be compared.
  156. # @param [String] version
  157. # The version of the file to compare to.
  158. 2 def diff_cmd(file, version)
  159. fail "The #{self.class} driver does not support the diff_cmd method!"
  160. end
  161. # Returns what is considered to be the top-level root directory by the revision control system.
  162. #
  163. # In the case of an application's revision controller (returned by Origen.app.rc) this method will often
  164. # return the same directory as Origen.root. However in some cases an application owner may choose to store
  165. # their application in a sub directory of a larger project entity that is revision controlled. In that
  166. # case Origen.root will return the sub directory and this method will return the top-level directory
  167. # of the wider project.
  168. 2 def root
  169. fail "The #{self.class} driver does not support the root method!"
  170. end
  171. # Returns the name of the current branch in the local workspace
  172. 2 def current_branch
  173. fail "The #{self.class} driver does not support the current_branch method!"
  174. end
  175. # Returns true if the revision controller object uses Design Sync
  176. 2 def dssc?
  177. is_a?(DesignSync)
  178. end
  179. 2 alias_method :design_sync?, :dssc?
  180. # Returns true if the revision controller object uses Git
  181. 2 def git?
  182. 190 is_a?(Git) # :-)
  183. end
  184. # Returns true if the revision controller object uses Perforce
  185. 2 def p4?
  186. is_a?(Perforce)
  187. end
  188. 2 alias_method :perforce?, :p4?
  189. # Returns true if the revision controller object uses Subversion
  190. 2 def svn?
  191. is_a?(Subversion) # :-)
  192. end
  193. 2 alias_method :subversion?, :svn?
  194. # rubocop:enable Lint/UnusedMethodArgument
  195. 2 private
  196. 2 def clean_path(path = nil, options = {})
  197. 2 path, options = nil, path if path.is_a?(Hash)
  198. 2 if path
  199. paths = to_local(path)
  200. else
  201. 2 paths = [local.to_s]
  202. end
  203. 2 [paths, options]
  204. end
  205. # Converts a given path string to files/directories to an array of absolute paths to the
  206. # resources within the local directory.
  207. # The input can either contain a path to the local directory, or the remote.
  208. #
  209. # @example
  210. # to_local("config/application.rb sync://sync-15088:15088/Projects/common_tester_blocks/origen/lib")
  211. # # => ["/home/r49409/origen/config/application.rb", "/home/r49409/origen/lib"]
  212. 2 def to_local(path)
  213. local_abs_paths = []
  214. path.to_s.split(/\s+/).each do |p|
  215. if p =~ /^#{remote}/
  216. p.sub!(/^#{remote}/, '')
  217. p.slice!(0) if p =~ /^\//
  218. p = "#{local}/#{p}"
  219. else
  220. if Pathname.new(p).absolute?
  221. # No action required
  222. else
  223. p = "#{local}/#{p}"
  224. end
  225. end
  226. local_abs_paths << p
  227. end
  228. local_abs_paths
  229. end
  230. # If the supplied tag looks like a semantic version number, then make sure it has the
  231. # 'v' prefix
  232. 2 def prefix_tag(tag)
  233. tag = Origen::VersionString.new(tag)
  234. if tag.semantic?
  235. tag.prefixed
  236. else
  237. tag
  238. end
  239. end
  240. 2 def initialize_local_dir(options = {})
  241. 2 FileUtils.mkdir_p(local.to_s) unless local.exist?
  242. end
  243. end
  244. end
  245. end

lib/origen/revision_control/git.rb

36.12% lines covered

227 relevant lines. 82 lines covered and 145 lines missed.
    
  1. 2 module Origen
  2. 2 module RevisionControl
  3. 2 class Git < Base
  4. # Returns the origin for the PWD
  5. 2 def self.origin
  6. 2 git('remote --verbose', verbose: false).each do |remote|
  7. 2 if remote =~ /^origin\s+([^\s]+)/
  8. 2 return Regexp.last_match(1)
  9. end
  10. end
  11. nil
  12. end
  13. # Returns the Git version number from the current runtime environment (as a string)
  14. 2 def self.version
  15. @version ||= begin
  16. version = nil
  17. git('--version', verbose: false).each do |line|
  18. if line =~ /git version (\d+(\.\d+)+)/
  19. version = Regexp.last_match(1)
  20. break
  21. end
  22. end
  23. if version
  24. version
  25. else
  26. Origen.log.warning 'Failed to determine the current Git version, proceeding by assuming version 2.0.0'
  27. '2.0.0'
  28. end
  29. end
  30. end
  31. 2 def build(options = {})
  32. if Dir["#{local}/*"].empty? || options[:force]
  33. FileUtils.rm_rf(local.to_s)
  34. # Not using the regular 'git' method here since the local dir doesn't exist to CD into
  35. system "git clone #{remote} #{local}"
  36. else
  37. fail "The requested workspace is not empty: #{local}"
  38. end
  39. end
  40. 2 def checkout(path = nil, options = {})
  41. paths, options = clean_path(path, options)
  42. # Pulls latest metadata from server, does not change workspace
  43. git 'fetch', options
  44. version = options[:version] || current_branch
  45. if version == 'HEAD'
  46. puts "Sorry, but you are not currently on a branch and I don't know which branch you want to checkout"
  47. puts 'Please supply a branch name as the version to checkout the latest version of it, e.g. origen rc co -v develop'
  48. exit 1
  49. end
  50. if options[:force]
  51. version = "origin/#{version}" if remote_branch?(version)
  52. if paths == [local.to_s]
  53. git "reset --hard #{version}", options
  54. else
  55. git 'reset HEAD'
  56. git 'pull', options
  57. git "checkout #{version} #{paths.join(' ')}", options
  58. end
  59. else
  60. if paths.size > 1 || paths.first != local.to_s
  61. fail 'The Git driver does not support partial merge checkout, it has to be the whole workspace'
  62. end
  63. git 'reset HEAD'
  64. res = git 'stash', options
  65. stashed = !res.any? { |l| l =~ /^No local changes to save/ }
  66. git 'pull', options
  67. git "checkout #{version}", options
  68. if stashed
  69. result = git 'stash pop', { check_errors: false }.merge(options)
  70. conflicts = []
  71. result.each do |line|
  72. if line =~ /CONFLICT.* (.*)$/
  73. conflicts << Regexp.last_match(1)
  74. end
  75. end
  76. git 'reset HEAD'
  77. unless conflicts.empty?
  78. Origen.log.info ''
  79. Origen.log.error 'Your local changes could not automatically merged into the following files, open them to fix the conflicts:'
  80. conflicts.each do |conflict|
  81. Origen.log.error " #{conflict}"
  82. end
  83. end
  84. end
  85. end
  86. paths
  87. end
  88. 2 def checkin(path = nil, options = {})
  89. paths, options = clean_path(path, options)
  90. # Can't check in unless we have the latest
  91. if options[:force] && !options[:initial]
  92. # Locally check in the given files
  93. checkin(paths.join(' '), no_push: true, verbose: false, comment: options[:comment])
  94. local_rev = current_commit(short: false)
  95. # Pull latest
  96. checkout
  97. # Restore the given files to our previous version
  98. # Errors are ignored here since this can fail if the given file didn't exist until now,
  99. # in that case we already implicitly have the previous version
  100. git("checkout #{local_rev} -- #{paths.join(' ')}", check_errors: false)
  101. # Then proceed with checking them in as latest
  102. else
  103. checkout unless options[:initial]
  104. end
  105. cmd = 'add'
  106. if options[:unmanaged]
  107. cmd += ' -A'
  108. else
  109. cmd += ' -u' unless options[:unmanaged]
  110. end
  111. cmd += " #{paths.join(' ')}"
  112. git cmd, options
  113. if changes_pending_commit?
  114. cmd = 'commit'
  115. if options[:comment] && !options[:comment].strip.empty?
  116. cmd += " -m \"#{options[:comment].strip}\""
  117. else
  118. cmd += " -m \"No comment!\""
  119. end
  120. if options[:author]
  121. if options[:author].respond_to?(:name_and_email)
  122. author = options[:author].name_and_email
  123. else
  124. author = "#{options[:author]} <>"
  125. end
  126. cmd += " --author=\"#{author}\""
  127. end
  128. if options[:time]
  129. cmd += " --date=\"#{options[:time].strftime('%a %b %e %H:%M:%S %Y %z')}\""
  130. end
  131. git cmd, options
  132. end
  133. unless options[:no_push]
  134. cmd = "push origin #{current_branch}"
  135. cmd += ' -u' if options[:initial]
  136. git cmd
  137. end
  138. paths
  139. end
  140. # Returns true if the current user can checkin to the given repo (means has permission
  141. # to push in Git terms)
  142. 2 def can_checkin?
  143. # dry run attempting to create a new remote branch named OrigenWritePermissionsTest
  144. git('push --dry-run origin origin:refs/heads/OrigenWritePermissionsTest', verbose: false)
  145. true
  146. rescue
  147. false
  148. end
  149. 2 def changes(dir = nil, options = {})
  150. paths, options = clean_path(dir, options)
  151. options = {
  152. verbose: false
  153. }.merge(options)
  154. # Pulls latest metadata from server, does not change workspace
  155. git 'fetch', options
  156. version = options[:version] || 'HEAD'
  157. objects = {}
  158. objects[:added] = git("diff --name-only --diff-filter=A #{version} #{paths.first}", options).map(&:strip)
  159. objects[:removed] = git("diff --name-only --diff-filter=D #{version} #{paths.first}", options).map(&:strip)
  160. objects[:changed] = git("diff --name-only --diff-filter=M #{version} #{paths.first}", options).map(&:strip)
  161. objects[:present] = !objects[:added].empty? || !objects[:removed].empty? || !objects[:changed].empty?
  162. # Return full paths
  163. objects[:added].map! { |i| "#{paths.first}/" + i }
  164. objects[:removed].map! { |i| "#{paths.first}/" + i }
  165. objects[:changed].map! { |i| "#{paths.first}/" + i }
  166. objects
  167. end
  168. 2 def local_modifications(dir = nil, options = {})
  169. 2 paths, options = clean_path(dir, options)
  170. options = {
  171. 2 verbose: false
  172. }.merge(options)
  173. 2 cmd = 'diff --name-only'
  174. 2 dir = " #{paths.first}"
  175. 2 unstaged = git(cmd + dir, options).map(&:strip)
  176. 2 staged = git(cmd + ' --cached' + dir, options).map(&:strip)
  177. 2 unstaged + staged
  178. end
  179. 2 def unmanaged(dir = nil, options = {})
  180. paths, options = clean_path(dir, options)
  181. options = {
  182. verbose: false
  183. }.merge(options)
  184. cmd = "ls-files #{paths.first} --exclude-standard --others"
  185. git(cmd, options).map(&:strip)
  186. end
  187. 2 def diff_cmd(file, version = nil)
  188. if version
  189. "git difftool --tool tkdiff -y #{prefix_tag(version)} #{file}"
  190. else
  191. "git difftool --tool tkdiff -y #{file}"
  192. end
  193. end
  194. 2 def tag(id, options = {})
  195. id = VersionString.new(id)
  196. id = id.prefixed if id.semantic?
  197. if options[:comment]
  198. git "tag -a #{id} -m \"#{options[:comment]}\""
  199. else
  200. git "tag #{id}"
  201. end
  202. git "push origin #{id}"
  203. end
  204. 2 def root
  205. 8 Pathname.new(git('rev-parse --show-toplevel', verbose: false).first.strip)
  206. end
  207. 2 def current_branch
  208. 2 git('rev-parse --abbrev-ref HEAD', verbose: false).first
  209. end
  210. 2 def current_commit(options = {})
  211. options = {
  212. 2 short: true
  213. }.merge(options)
  214. 2 commit = git('rev-parse HEAD', verbose: false).first
  215. 2 if options[:short]
  216. 2 commit[0, 11]
  217. else
  218. commit
  219. end
  220. end
  221. # Returns true if the given tag already exists
  222. 2 def tag_exists?(tag)
  223. git('fetch', verbose: false) unless @all_tags_fetched
  224. @all_tags_fetched = true
  225. git('tag', verbose: false).include?(tag.to_s)
  226. end
  227. # Returns true if the given string matches a branch name in the remote repo
  228. # Origen.app.rc.remote_branch?("master") # => true
  229. # Origen.app.rc.remote_branch?("feature/exists") # => true
  230. # Origen.app.rc.remote_branch?("feature/does_not_exist") # => false
  231. 2 def remote_branch?(str)
  232. # Github doesn't like the ssh:// for this command, whereas Stash seems
  233. # to require it.
  234. if github?
  235. rem = remote_without_protocol
  236. else
  237. rem = remote
  238. end
  239. # check if matches 40 digit hex string followed by branch name
  240. git("ls-remote --heads #{remote} #{str}", verbose: false).any? do |line|
  241. line =~ /^[0-9a-f]{40}\s+[a-zA-Z]/
  242. end
  243. end
  244. 2 def initialized?(options = {})
  245. 2 @hierarchy_searched ||= begin
  246. 2 path = @local.dup
  247. 2 until path.root? || File.exist?("#{local}/.git")
  248. if File.exist?("#{path}/.git")
  249. if options[:allow_local_adjustment]
  250. @local = path
  251. else
  252. fail "Requested local repository #{local} is within existing local repository #{path}"
  253. end
  254. else
  255. path = path.parent
  256. end
  257. end
  258. 2 true
  259. end
  260. 2 File.exist?("#{local}/.git") &&
  261. 2 git('remote -v', verbose: false).any? { |r| r =~ /#{remote_without_protocol_and_user}/ || r =~ /#{remote_without_protocol_and_user.to_s.gsub(':', "\/")}/ } &&
  262. 6 !git('status', verbose: false).any? { |l| l =~ /^#? ?(Initial commit|No commits yet)$/ }
  263. end
  264. # Delete everything in the given directory, or the whole repo
  265. 2 def delete_all(dir = nil, options = {})
  266. paths, options = clean_path(dir, options)
  267. files = git("ls-files #{paths.first}")
  268. FileUtils.rm_f files
  269. end
  270. # A class method is provided to fetch the user name since it is useful to have access
  271. # to this when outside of an application workspace, e.g. when creating a new app
  272. 2 def self.user_name
  273. 2 git('config user.name', verbose: false).first
  274. rescue
  275. nil
  276. end
  277. # A class method is provided to fetch the user email since it is useful to have access
  278. # to this when outside of an application workspace, e.g. when creating a new app
  279. 2 def self.user_email
  280. git('config user.email', verbose: false).first
  281. rescue
  282. nil
  283. end
  284. 2 def user_name
  285. self.class.user_name
  286. end
  287. 2 def user_email
  288. self.class.user_email
  289. end
  290. # Returns true if the remote points to a github url
  291. 2 def github?
  292. !!(remote.to_s =~ /github.com/)
  293. end
  294. 2 private
  295. 2 def remote_without_protocol
  296. 2 Pathname.new(remote.sub(/^.*:\/\//, ''))
  297. end
  298. 2 def remote_without_protocol_and_user
  299. 2 Pathname.new(remote_without_protocol.to_s.sub(/^.*@/, ''))
  300. end
  301. 2 def create_gitignore
  302. c = Origen::Generator::Compiler.new
  303. c.compile "#{Origen.top}/templates/git/gitignore.erb",
  304. output_directory: local,
  305. quiet: true,
  306. check_for_changes: false
  307. FileUtils.mv "#{local}/gitignore", "#{local}/.gitignore"
  308. end
  309. 2 def changes_pending_commit?
  310. !(git('status --verbose', verbose: false).last =~ /^(no changes|nothing to commit|nothing added to commit but untracked files present)/)
  311. end
  312. 2 def initialize_local_dir(options = {})
  313. 2 return if options[:build_method] == :clone
  314. 2 super
  315. 2 unless initialized?(options)
  316. Origen.log.debug "Initializing Git workspace at #{local}"
  317. git 'init'
  318. git 'remote remove origin', verbose: false, check_errors: false
  319. git "remote add origin #{remote}", check_errors: false
  320. end
  321. end
  322. 2 def git(command, options = {})
  323. 20 options[:local] = local
  324. 20 self.class.git(command, options)
  325. end
  326. # Execute a git operation, the resultant output is returned in an array
  327. 2 def self.git(command, options = {})
  328. options = {
  329. 24 check_errors: true,
  330. verbose: true
  331. }.merge(options)
  332. 24 output = []
  333. 24 if options[:verbose]
  334. Origen.log.info "git #{command}"
  335. Origen.log.info ''
  336. end
  337. 24 chdir options[:local] do
  338. 24 Open3.popen2e("git #{command}") do |_stdin, stdout_err, wait_thr|
  339. 24 while line = stdout_err.gets
  340. 30 Origen.log.info line.strip if options[:verbose]
  341. 30 unless line.strip.empty?
  342. 28 output << line.strip
  343. end
  344. end
  345. 24 exit_status = wait_thr.value
  346. 24 unless exit_status.success?
  347. if options[:check_errors]
  348. if output.any? { |l| l =~ /Not a git repository/ }
  349. fail RevisionControlUninitializedError
  350. else
  351. fail GitError, "This command failed: 'git #{command}'"
  352. end
  353. end
  354. end
  355. end
  356. end
  357. 24 output
  358. end
  359. 2 def self.chdir(dir)
  360. 24 if dir
  361. 20 Dir.chdir dir do
  362. 20 yield
  363. end
  364. else
  365. 4 yield
  366. end
  367. end
  368. end
  369. end
  370. end

lib/origen/specs/creation_info.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # Ruby Data Class that contains Creation Information for the IP Block
  4. 2 class Creation_Info
  5. 2 attr_accessor :author, :date, :revision, :source, :tool, :tool_version, :ip_version, :ip_block_name
  6. # Initialize the Creation Info block to store data for latest version of the file.
  7. #
  8. # ==== Parameters
  9. #
  10. # * author # Author/Subject Matter Expert for the IP Block
  11. # * date # Date that the File was released to Downstream Audiences
  12. # ==== Source Information
  13. #
  14. # * :revision # Revision Information
  15. # * :source # Where the Information came from
  16. # * :ip_block_name # Block Name for the IP. e.g. DDR for DDRC1, DDRC2; I2C for I2C1, I2C2
  17. #
  18. # ==== Tool Info
  19. #
  20. # * :tool # Tool that created the initial XML file
  21. # * :version # Version of the Tool that created the XML file
  22. #
  23. # ==== Example
  24. #
  25. # Creation_Info.new("author", "07/10/2015", :revision => "5.4", :source => "CSV", :tool => "oRiGeN", :tool_version => "0.0.6")
  26. 2 def initialize(author, date, ip_version, src_info = {}, tool_info = {})
  27. 4 @author = author
  28. 4 @date = date
  29. 4 @ip_version = ip_version
  30. 4 @revision = src_info[:revision]
  31. 4 @source = src_info[:source]
  32. 4 @ip_block_name = src_info[:ip_block_name]
  33. 4 @tool = tool_info[:tool]
  34. 4 @tool_version = tool_info[:version]
  35. end
  36. end
  37. end
  38. end

lib/origen/specs/doc_resource.rb

35.71% lines covered

70 relevant lines. 25 lines covered and 45 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store text information to help with documentation processes
  4. 2 class Doc_Resource
  5. # Mode is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  6. 2 attr_accessor :mode
  7. # Type is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  8. # Usual values
  9. #
  10. # * DC -> Direct Current
  11. # * AC -> Alternate Current
  12. # * Temp -> Temperature
  13. # * Supply -> Supply
  14. 2 attr_accessor :type
  15. # SubType is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  16. 2 attr_accessor :sub_type
  17. # Audience is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  18. 2 attr_accessor :audience
  19. # Table Title that should appear for the table. If blank, generic Table Title will be used
  20. # Hash is created from mode, type, sub_type, and audience.
  21. 2 attr_accessor :table_title
  22. # Note References that should be referenced within the table title
  23. 2 attr_accessor :note_refs
  24. # Exhibit References that should be referenced within the table title
  25. 2 attr_accessor :exhibit_refs
  26. # DITA Formatted Text that appears before the table
  27. 2 attr_accessor :before_table
  28. # DITA Formatted Text that appears after the table
  29. 2 attr_accessor :after_table
  30. # Documentation Options that will change the appearance of the output.
  31. 2 attr_accessor :doc_options
  32. # Initialize the Class
  33. 2 def initialize(selector = {}, table_title = {}, text = {}, options = {})
  34. 29 @mode = selector[:mode]
  35. 29 @type = selector[:type]
  36. 29 @sub_type = selector[:sub_type]
  37. 29 @audience = selector[:audience]
  38. 29 @table_title = table_title[:title]
  39. 29 @note_refs = table_title[:note_refs]
  40. 29 @exhibit_refs = table_title[:exhibit_refs]
  41. 29 @before_table = text[:before]
  42. 29 @after_table = text[:after]
  43. 29 @doc_options = options
  44. end
  45. # Converts to an XML file.
  46. 2 def to_xml
  47. tmp = {}
  48. tmp['mode'] = @mode unless @mode.nil?
  49. tmp['type'] = @type unless @type.nil?
  50. tmp['sub_type'] = @sub_type unless @sub_type.nil?
  51. tmp['audience'] = @audience unless @audience.nil?
  52. doc_resource_ml = Nokogiri::XML::Builder.new do |xml|
  53. xml.doc_resource(tmp.each do |t, d|
  54. "#{t}=\"#{d}\""
  55. end
  56. ) do
  57. unless @table_title.nil? && @note_refs.size == 0 && @exhibit_refs.size == 0
  58. unless @note_refs.first.to_s.size == 0
  59. unless @exhibit_refs.first.to_s.size == 0
  60. xml.title do
  61. unless @table_title.nil?
  62. xml.Text @table_title.to_s
  63. end
  64. unless @note_refs.size == 0
  65. unless @note_refs.first.to_s.size == 0
  66. xml.noteRefs do
  67. @note_refs = [@note_refs] unless @note_refs.is_a? Array
  68. @note_refs.each do |note_ref|
  69. unless note_ref.to_s.size == 0
  70. xml.noteRef(href: note_ref.to_s)
  71. end # unless note_ref.to_s.size == 0
  72. end # @note_refs.each do |note_ref|
  73. end # xml.noteRefs do
  74. end # unless @note_refs.first.to_s.size == 0
  75. end # unless @note_refs.size == 0
  76. unless @exhibit_refs.size == 0
  77. unless @exhibit_refs.first.to_s.size == 0
  78. xml.exhibitRefs do
  79. @exhibit_refs = [@exhibit_refs] unless @exhibit_refs.is_a? Array
  80. @exhibit_refs.each do |exhibit_ref|
  81. unless exhibit_ref.to_s.size == 0
  82. xml.exhibitRef(href: exhibit_ref.to_s)
  83. end # unless exhibit_ref.to_s.size == 0
  84. end # @exhibit_refs.each do |exhibit_ref|
  85. end # xml.exhibitRefs do
  86. end # unless @exhibit_refs.first.to_s.size == 0
  87. end # unless @exhibit_refs.size == 0
  88. end # xml.title.done
  89. end # unless @exhibit_refs.to_s.size == 0
  90. end # unless @note_refs.to_s.size == 0
  91. end # unless @table_title.nil? && @note_refs.size == 0 && @exhibit_refs.size == 0
  92. unless @before_table.nil? && @after_table.nil?
  93. xml.paragraphs do
  94. unless @before_table.nil?
  95. if (@before_table.is_a? Nokogiri::XML::Node) || (@before_table.is_a? Nokogiri::XML::Element)
  96. xml.before_table do
  97. if @before_table.name == 'body'
  98. xml << @before_table.children.to_xml
  99. else
  100. xml << @before_table.to_xml
  101. end
  102. end
  103. else
  104. xml.before_table @before_table
  105. end
  106. end
  107. unless @after_table.nil?
  108. if (@after_table.is_a? Nokogiri::XML::Node) || (@after_table.is_a? Nokogiri::XML::Element)
  109. xml.after_table do
  110. if @after_table.name == 'body'
  111. xml << @after_table.children.to_xml
  112. else
  113. xml << @after_table.to_xml
  114. end
  115. end
  116. else
  117. xml.after_table @after_table
  118. end
  119. end
  120. end # xml.paragraphs do
  121. end # unless @before_table.nil? && @after_table.nil?
  122. end # xml.doc_resource
  123. end # doc_resource_ml = Nokogiri::
  124. doc_resource_ml.doc.at_xpath('doc_resource').to_xml
  125. end # to_xml
  126. end
  127. end
  128. end

lib/origen/specs/documentation.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store documentation map that the user can change
  4. 2 class Documentation
  5. # Level that Section is at. Allows for a key to be found.
  6. 2 attr_accessor :level
  7. # This is the Section Header for the Documentation Map. Usually these are main headers
  8. # Examples:
  9. # I. Overall DC Electricals
  10. # II. General AC Charactertistics
  11. # III. Power Sequencing
  12. 2 attr_accessor :section
  13. # This is the subsection header for the Documentation Map. These are found under main headers
  14. # Examples
  15. # I. Overall DC electrical
  16. # A. Absolute Maximum Ratings
  17. # B. Recommend Operating Conditions
  18. # C. Output Driver
  19. 2 attr_accessor :subsection
  20. # Exhibit References that should be referenced within the table title
  21. 2 attr_accessor :interface
  22. # Mode is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  23. 2 attr_accessor :mode
  24. # Type is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  25. # Usual values
  26. #
  27. # * DC -> Direct Current
  28. # * AC -> Alternate Current
  29. # * Temp -> Temperature
  30. # * Supply -> Supply
  31. 2 attr_accessor :type
  32. # SubType is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  33. 2 attr_accessor :sub_type
  34. # Audience is part of the 4-D Hash for the Tables. Corresponds to Spec 4-D Hash
  35. 2 attr_accessor :audience
  36. # DITA Formatted Text that appears before the table
  37. 2 attr_accessor :link
  38. # Applicable Devices for the map
  39. 2 attr_accessor :applicable_devices
  40. # Initialize the Class
  41. 2 def initialize(header_info = {}, selection = {}, applicable_devs = [], link = nil)
  42. 13 @level = header_info[:level]
  43. 13 @section = header_info[:section]
  44. 13 @subsection = header_info[:subsection]
  45. 13 @interface = selection[:interface]
  46. 13 @mode = selection[:mode]
  47. 13 @type = selection[:type]
  48. 13 @sub_type = selection[:sub_type]
  49. 13 @audience = selection[:audience]
  50. 13 @applicable_devices = applicable_devs
  51. 13 @link = link
  52. end
  53. end
  54. end
  55. end

lib/origen/specs/exhibit.rb

100.0% lines covered

27 relevant lines. 27 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store spec exhibit information used to document IP
  4. 2 class Exhibit
  5. # ID for the exhibit. This allows the exhibit to reference easier
  6. 2 attr_accessor :id
  7. # Type of exhibit. Currently only :fig is supported. In the future, this could be :topic or :table or anything else
  8. 2 attr_accessor :type
  9. # Title for the Exhibit.
  10. 2 attr_accessor :title
  11. # Description for the Exhibit
  12. 2 attr_accessor :description
  13. # Reference link
  14. 2 attr_accessor :reference
  15. # Markup needed for the exhibit
  16. 2 attr_accessor :markup
  17. # Do we include the exhibit in this block
  18. 2 attr_accessor :include_exhibit
  19. # Block ID that this exhibit is being used in.
  20. 2 attr_accessor :block_id
  21. # Title Override. Allows for the SoC to override the title so that it makes more sense
  22. 2 attr_accessor :title_override
  23. # Reference Override. This allows for the SoC to use a different figure (e.g. Power Supplies are different)
  24. 2 attr_accessor :reference_override
  25. # Description Override. This allows for the SoC to use a different description
  26. 2 attr_accessor :description_override
  27. 2 def initialize(id, type, overrides, options = {})
  28. 8 @id = id
  29. 8 @type = type
  30. 8 @title = options[:title]
  31. 8 @description = options[:description]
  32. 8 @reference = options[:reference]
  33. 8 @title_override = overrides[:title]
  34. 8 @reference_override = overrides[:reference]
  35. 8 @description_override = overrides[:description]
  36. 8 @markup = options[:markup]
  37. 8 @include_exhibit = true
  38. 8 @include_exhibit = options[:include_exhibit] unless options[:include_exhibit].nil?
  39. 8 @block_id = options[:block_id]
  40. end
  41. end
  42. end
  43. end

lib/origen/specs/mode_select.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store mode select for IP
  4. 2 class Mode_Select
  5. # Block Name at the SoC (e.g. DDRC1, DDRC2, DDRC3)
  6. 2 attr_accessor :block
  7. # Data Sheet Header/Group Name
  8. 2 attr_accessor :ds_header
  9. # Block Use at the SoC Level
  10. 2 attr_accessor :usage
  11. # Mode Reference Name
  12. 2 attr_accessor :mode
  13. # SoC Supports this mode?
  14. 2 attr_accessor :supported
  15. # SoC Supply List
  16. 2 attr_accessor :supply
  17. # SoC Supply Voltage Level
  18. 2 attr_accessor :supply_level
  19. # Use Information from different data source
  20. 2 attr_accessor :diff_loc
  21. # Location of the block to read
  22. 2 attr_accessor :location
  23. # There are three sub-blocks of information in Mode Select
  24. # * block_information:
  25. # ** name : The name of the block as instiniated in the SoC
  26. # ** ds_header: Data Sheet Header/Group. Allows for multiple instantation to be grouped under one header in datasheet or allows for them to broken out
  27. # ** usage: Block is used in this SoC {Could be starting point for license plate support}
  28. # ** location: File path to the specml location
  29. #
  30. # * mode_usage:
  31. # ** mode: The mode name at the IP Level
  32. # ** usage: Does this IP in this SoC support this mode?
  33. #
  34. # * power_information:
  35. # ** supply: Name of the supply for that Interface.
  36. # ** voltage_level: Array of the possible values for this supply e.g. [1.8, 2.5, 3.3] or [1.8]
  37. # ** use_diff: Use information from a different location
  38. 2 def initialize(block_information, mode_usage, power_information)
  39. 6 @block = block_information[:name]
  40. 6 @ds_header = block_information[:ds_header]
  41. 6 @usage = block_information[:usage]
  42. 6 @location = block_information[:location]
  43. 6 @mode = mode_usage[:mode]
  44. 6 @supported = mode_usage[:supported]
  45. 6 @supply = power_information[:supply]
  46. 6 @supply_level = power_information[:voltage_level]
  47. 6 @diff_loc = power_information[:use_diff]
  48. end
  49. end
  50. end
  51. end

lib/origen/specs/note.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store spec note information used to document IP
  4. 2 class Note
  5. # id is the id for the note. The goal for the id is to allow multiple specs to reference one note.
  6. # spec.notes = [id1, id2, id3]
  7. # spec1.notes = [id1, id4, id5]
  8. 2 attr_accessor :id
  9. # Type should be :ac or :dc, but this might have been phased out.
  10. # TODO: Check to see if :type has been deprecated or is still needed
  11. 2 attr_accessor :type
  12. # Mode will match the mode that this note belongs to.
  13. # TODO: Check to see if :mode has been deprecated or is still needed
  14. 2 attr_accessor :mode
  15. # Audience should be :ac or :dc, but this might have been phased out.
  16. # TODO: Check to see if :type has been deprecated or is still needed
  17. 2 attr_accessor :audience
  18. # Plain text of the note. No Mark-up allowed in this field.
  19. 2 attr_accessor :text
  20. # Note Number. Optional. If not set, then DITA will make the number
  21. 2 attr_accessor :number
  22. # Markup of the text field. Currently markup has been tested with
  23. #
  24. # * DITA
  25. # * XML
  26. # * HTML
  27. #
  28. # Need to test the following markup
  29. #
  30. # * Markdown
  31. 2 attr_accessor :markup
  32. # Internal comment that could be used to know why the note was needed. Think of this as a breadcrumb
  33. # to find out about more information on the note.
  34. 2 attr_accessor :internal_comment
  35. # Initialize the class
  36. 2 def initialize(id, type, options = {})
  37. 17 @id = id
  38. 17 @type = type
  39. 17 @mode = options[:mode]
  40. 17 @audience = options[:audience]
  41. 17 @text = options[:text]
  42. 17 @markup = options[:markup]
  43. 17 @internal_comment = options[:internal_comment]
  44. 17 @number = options[:number]
  45. end
  46. end
  47. end
  48. end

lib/origen/specs/override.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store override information for specified specs on instantiated IP
  4. 2 class Override
  5. 2 attr_accessor :block, :usage, :spec_ref, :mode_ref, :sub_type, :audience, :minimum, :maximum, :typical, :disable, :hidespec
  6. 2 def initialize(block_options = {}, find_spec = {}, values = {}, options = {})
  7. 9 @block = block_options[:block]
  8. 9 @usage = block_options[:usage]
  9. 9 @spec_ref = find_spec[:spec_id]
  10. 9 @mode_ref = find_spec[:mode_ref]
  11. 9 @sub_type = find_spec[:sub_type]
  12. 9 @audience = find_spec[:audience]
  13. 9 @minimum = values[:min]
  14. 9 @maximum = values[:max]
  15. 9 @typical = values[:typ]
  16. 9 @disable = options[:disable]
  17. 9 @hidespec = options[:hidespec]
  18. end
  19. end
  20. end
  21. end

lib/origen/specs/power_supply.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store Power Supply Information at the SoC Level
  4. 2 class Power_Supply
  5. # Generic Power Supply Name. For example:
  6. # * GVDD
  7. # * DVDD
  8. # * TVDD
  9. # * EVDD
  10. 2 attr_accessor :generic
  11. # The Actual Power Supply Name. For example, GVDD could be the generic name and actual names can be G1VDD and G2VDD.
  12. # GVDD ==> {G1VDD, G2VDD, G3VDD}
  13. # DVDD ==> {D1VDD, D2VDD}
  14. 2 attr_accessor :actual
  15. # Voltages for the power supply. Needs to be supplied by a different source
  16. # Voltages is an array for all possible values for that power supply
  17. # DVDD ==>
  18. # * 1.8 V
  19. # * 3.3 V
  20. 2 attr_accessor :voltages
  21. # Display Name for the Voltage. Will be in html/dita code
  22. # G1VDD --> G1V<sub>DD</sub>
  23. 2 attr_accessor :display_name
  24. # Input Display Name for the Voltage
  25. # G1VDD --> G1V<sub>IN</sub>
  26. 2 attr_accessor :input_display_name
  27. # Output Displat Name for the Voltage
  28. # G1VDD --> G1V<sub>OUT</sub>
  29. 2 attr_accessor :output_display_name
  30. # Initialize the variables
  31. 2 def initialize(gen, act)
  32. 20 Origen.deprecate 'Origen::Specs::Power_Supply is deprecated, use Origen::PowerDomains::PowerDomain instead'
  33. 20 @generic = gen
  34. 20 @actual = act
  35. 20 @voltages = []
  36. 20 @display_name = ''
  37. 20 @input_display_name = ''
  38. 20 @output_display_name = ''
  39. end
  40. 2 def update_input
  41. 1 @input_display_name = change_subscript('IN')
  42. end
  43. 2 def update_output
  44. 1 @output_display_name = change_subscript('OUT')
  45. end
  46. 2 def change_subscript(new_subscript)
  47. 2 temp_display_name = @display_name.dup
  48. 2 sub_input = temp_display_name.at_css 'sub'
  49. 2 sub_input.content = new_subscript unless sub_input.nil?
  50. 2 temp_display_name
  51. end
  52. end
  53. end
  54. end

lib/origen/specs/spec.rb

44.9% lines covered

147 relevant lines. 66 lines covered and 81 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. 2 class Spec
  4. 2 autoload :Note, 'origen/specs/note'
  5. 2 autoload :Exhibit, 'origen/specs/exhibit'
  6. 2 include Checkers
  7. 2 extend Checkers
  8. 2 SpecAttribute = Struct.new(:name, :type, :required, :author, :description)
  9. 2 Limit = Struct.new(:exp) do
  10. 2 def value
  11. 285 Origen::Specs::Spec.send(:evaluate_limit, exp)
  12. end
  13. end
  14. 2 TYPES = Origen::Specs::SPEC_TYPES
  15. ATTRS = {
  16. 2 ip_name: SpecAttribute.new(:ip_name, Symbol, true, :design, 'The parent IP object of the specification'),
  17. name: SpecAttribute.new(:name, Symbol, true, :design, 'Specification Name'),
  18. type: SpecAttribute.new(:type, Symbol, true, :design, "Specification Type, acceptable values: #{TYPES}"),
  19. sub_type: SpecAttribute.new(:sub_type, Symbol, true, :design, 'Specification sub-type (e.g. :max_operating_condition)'),
  20. mode: SpecAttribute.new(:mode, Symbol, true, :design, 'Specification mode, inherited from the owning parent object'),
  21. symbol: SpecAttribute.new(:symbol, String, false, :design, 'Specification symbol, can contain HTML'),
  22. description: SpecAttribute.new(:description, String, false, :design, 'Specification description'),
  23. audience: SpecAttribute.new(:audience, Symbol, false, :design, 'Specification audience, acceptable values are :internal and :external'),
  24. min: SpecAttribute.new(:min, Limit, false, :design, 'Specification minimum limit. The limit expression is displayed, not a resolved value'),
  25. min_ovr: SpecAttribute.new(:min_ovr, Limit, false, :design, 'Specification minimum limit at SoC level. The limit expression is displaye,d not a resolved value'),
  26. max: SpecAttribute.new(:max, Limit, false, :design, 'Specification maximum limit. The limit expression is displayed, not a resolved value'),
  27. max_ovr: SpecAttribute.new(:max_ovr, Limit, false, :design, 'Specification maximum limit at SoC level. The limit expression is displaye,d not a resolved value'),
  28. typ: SpecAttribute.new(:typ, Limit, false, :design, 'Specification typical limit. The limit expression is displayed, not a resolved value'),
  29. typ_ovr: SpecAttribute.new(:typ_ovr, Limit, false, :design, 'Specification typical limit at SoC level. The limit expression is displaye,d not a resolved value'),
  30. unit: SpecAttribute.new(:unit, String, false, :design, 'Specification unit of measure'),
  31. constraints: SpecAttribute.new(:constraints, String, false, :design, "Single logical expression or a CSV list of logical expressions required for the spec to be valid (e.g. 'GVDD == 1.2V'"),
  32. limit_type: SpecAttribute.new(:limit_type, Symbol, false, :design, 'Auto-generated attribute based on analysis of the spec limits. Acceptable values are :single_sided and :double_sided'),
  33. notes: SpecAttribute.new(:notes, Hash, false, :design, 'Specification notes'),
  34. hidespec: SpecAttribute.new(:hidespec, [String, Array], false, :design, 'Add the ability to hide specs based off license plate'),
  35. disposition_required: SpecAttribute.new(:disposition_required, TrueClass, false, :pde, 'Boolean representation of whether a specification needs a disposition based on silicon results or customer input'),
  36. priority: SpecAttribute.new(:priority, TrueClass, false, :pde, 'Integer value (1-4) to indicate which priority the cz for this spec will be: 1. Highest priority, for critical or historically risky specs 2. Medium priority, relatively low risk. Not required until all priority 1 specs have been handled 3. Lowest priority, very low risk, low performance specs 4. No plans to characterize'),
  37. target: SpecAttribute.new(:target, String, false, :pde, 'Specification target limit. Not used for pass/fail results but for data analysis'),
  38. guardband: SpecAttribute.new(:guardband, Limit, false, :pde, 'Specification guardband limit'),
  39. testable: SpecAttribute.new(:testable, TrueClass, false, :pde, 'Boolean representation of whether a specification is testable'),
  40. tested_at_probe: SpecAttribute.new(:tested_at_probe, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at probe'),
  41. tested_at_ft_hot: SpecAttribute.new(:tested_at_ft_hot, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test hot temperature'),
  42. tested_at_ft_ext_hot: SpecAttribute.new(:tested_at_ft_ext_hot, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test extended hot temperature'),
  43. tested_at_ft_cold: SpecAttribute.new(:tested_at_ft_cold, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test cold temperature'),
  44. tested_at_ft_ext_cold: SpecAttribute.new(:tested_at_ft_ext_cold, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test extended cold temperature'),
  45. tested_at_ft_room: SpecAttribute.new(:tested_at_ft_room, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested at final test room temperature'),
  46. guaranteed_by_prod_test: SpecAttribute.new(:guaranteed_by_prod_test, TrueClass, false, :pde, 'Boolean representation of whether a specification is guaranteed by production test'),
  47. guaranteed_by_proxy_test: SpecAttribute.new(:guaranteed_by_proxy_test, TrueClass, false, :pde, 'Boolean representation of whether a specification is guaranteed by production test via a proxy test such as BIST'),
  48. guaranteed_by_construction: SpecAttribute.new(:guaranteed_by_construction, TrueClass, false, :pde, 'Boolean representation of whether a specification is guaranteed by physical construction, design documentation required'),
  49. guaranteed_by_simulation: SpecAttribute.new(:guaranteed_by_simulation, TrueClass, false, :pde, 'Boolean representation of whether a specification is tested guaranteed by simulation, design documentation required'),
  50. cz_on_ate: SpecAttribute.new(:cz_on_ate, TrueClass, false, :pde, 'Boolean representation of whether a specification is characterized on ATE'),
  51. cz_ate_sample_size: SpecAttribute.new(:cz_ate_sample_size, Integer, false, :pde, 'Integer number representing the sample size of the split used for customer Cpk calculation as tested on ATE'),
  52. cz_ate_cpk: SpecAttribute.new(:cz_ate_cpk, Float, false, :pde, 'Float number representing the customer or representative Cpk of the specification as tested on ATE'),
  53. cz_on_bench: SpecAttribute.new(:cz_on_bench, TrueClass, false, :pde, 'Boolean representation of whether a specification is characterized on a bench setup'),
  54. cz_bench_sample_size: SpecAttribute.new(:cz_bench_sample_size, Integer, false, :pde, 'Integer number representing the sample size of the split used for customer Cpk calculation on a bench setup'),
  55. cz_bench_cpk: SpecAttribute.new(:cz_bench_cpk, Float, false, :pde, 'Float number representing the customer or representative Cpk of the specification as tested on a bench setup'),
  56. cz_on_system: SpecAttribute.new(:cz_on_system, TrueClass, false, :pde, 'Boolean representation of whether a specification is characterized in a system setup'),
  57. cz_system_sample_size: SpecAttribute.new(:cz_system_sample_size, Integer, false, :pde, 'Integer number representing the sample size of the split used for customer Cpk calculation in a system'),
  58. cz_system_cpk: SpecAttribute.new(:cz_system_cpk, Float, false, :pde, 'Float number representing the customer or representative Cpk of the specification as tested in a system')
  59. }
  60. 2 ATTRS.each do |_id, spec_attr|
  61. 86 class_eval("def #{spec_attr.name}(param=nil); param.nil? ? @#{spec_attr.name} : (@#{spec_attr.name} = param); end")
  62. end
  63. # There are at least three attributes needed to define a unique spec.
  64. # 1) name (e.g. :vdd)
  65. # 2) type (e.g. :dc) Possible values are [:dc, :ac, :temperature]
  66. # 3) mode (e.g. :global). mode defaults to the current mode found for the parent object
  67. # A mode is defined as a device state that requires some sequence of actions to be enabled.
  68. # A type is a classification moniker that exists without any stimulus required.
  69. # Some specs require a fourth attribute sub_type to be uniquely defined.
  70. # For example, a global device level VDD specification would require four attributes to be unique.
  71. # Here is an example of two spec definitions for a VDD power supply
  72. # name = :vdd, type: :dc, mode: :global, sub_type: typical_operating_conditions, typ = "1.0V +/- 30mV"
  73. # name = :vdd, type: :dc, mode: :global, sub_type: maximum_operating_conditions, min = -0.3V, max = 1.8V
  74. # Whereas a typical DDR timing specification might only need three attributes to be unique
  75. # name: :tddkhas, type: :ac, mode: ddr4dr2400, sub_type: nil
  76. 2 def initialize(name, type, mode, owner_name, &block)
  77. 23 @name = name_audit(name)
  78. 23 fail 'Specification names must be of types Symbol or String and cannot start with a number' if @name.nil?
  79. 23 @type = type
  80. 23 @sub_type = nil # not necessary to be able to find a unique spec, but required for some specs
  81. 23 @mode = mode
  82. 23 @ip_name = owner_name
  83. 23 @symbol = nil # Meant to be populated with HTML representing the way the spec name should look in a document
  84. 23 @description = nil
  85. 23 @min, @typ, @max, @target = nil, nil, nil, nil
  86. 23 @min_ovr, @typ_ovr, @typ_ovr = nil, nil, nil
  87. 23 @audience = nil
  88. 23 @notes = {}
  89. 23 @exhibits = {}
  90. 23 @testable = nil
  91. 23 @guardband = nil
  92. 23 (block.arity < 1 ? (instance_eval(&block)) : block.call(self)) if block_given?
  93. 23 fail "Spec type must be one of #{TYPES.join(', ')}" unless TYPES.include? type
  94. 23 @min = Limit.new(@min)
  95. 23 @max = Limit.new(@max)
  96. 23 @typ = Limit.new(@typ)
  97. 23 @min_ovr = Limit.new(@min_ovr)
  98. 23 @max_ovr = Limit.new(@max_ovr)
  99. 23 @typ_ovr = Limit.new(@typ_ovr)
  100. 23 @guardband = Limit.new(@guardband)
  101. 23 fail "Spec #{name} failed the limits audit!" unless limits_ok?
  102. end
  103. 2 def inspect
  104. 30 $dut.send(:specs_to_table_string, [self])
  105. rescue
  106. super
  107. end
  108. # Returns the trace_matrix name. The Trace Matrix Name is composed of
  109. # * @name
  110. # * @type
  111. # * @subtype
  112. # * @mode
  113. 2 def trace_matrix_name
  114. name_set = trace_matrix_name_choose
  115. ret_name = ''
  116. case name_set
  117. when 0
  118. ret_name = ''
  119. when 1
  120. ret_name = "#{@mode}"
  121. when 2
  122. ret_name = "#{@sub_type}"
  123. when 3
  124. ret_name = "#{@sub_type}_#{@mode}"
  125. when 4
  126. ret_name = "#{@type}"
  127. when 5
  128. ret_name = "#{@type}_#{@mode}"
  129. when 6
  130. ret_name = "#{@type}_#{@sub_type}"
  131. when 7
  132. ret_name = "#{@type}_#{@sub_type}_#{@mode}"
  133. when 8
  134. ret_name = "#{small_name}"
  135. when 9
  136. ret_name = "#{small_name}_#{@mode}"
  137. when 10
  138. ret_name = "#{small_name}_#{@sub_type}"
  139. when 11
  140. ret_name = "#{small_name}_#{@sub_type}_#{@mode}"
  141. when 12
  142. ret_name = "#{small_name}_#{@type}"
  143. when 13
  144. ret_name = "#{small_name}_#{@type}_#{@mode}"
  145. when 14
  146. ret_name = "#{small_name}_#{@type}_#{@sub_type}"
  147. when 15
  148. ret_name = "#{small_name}_#{@type}_#{@sub_type}_#{@mode}"
  149. else
  150. ret_name = 'Bad trace matrix code'
  151. end
  152. ret_name
  153. end
  154. # This will create the trace matrix name to be placed into a dita phrase element
  155. # End goal will be
  156. # {code:xml}
  157. # <ph audience="internal">trace_matrix_name</ph>
  158. # {code}
  159. 2 def trace_matrix_name_to_dita
  160. tmp_doc = Nokogiri::XML('<foo><bar /></foo>', nil, 'EUC-JP')
  161. tmp_node = Nokogiri::XML::Node.new('lines', tmp_doc)
  162. tmp_node1 = Nokogiri::XML::Node.new('i', tmp_doc)
  163. tmp_node.set_attribute('audience', 'trace-matrix-id')
  164. text_node1 = Nokogiri::XML::Text.new("[#{trace_matrix_name}]", tmp_node)
  165. tmp_node1 << text_node1
  166. tmp_node << tmp_node1
  167. tmp_node.at_xpath('.').to_xml
  168. end
  169. 2 def method_missing(method, *args, &block)
  170. 48 ivar = "@#{method.to_s.gsub('=', '')}"
  171. 48 ivar_sym = ":#{ivar}"
  172. 48 if method.to_s =~ /=$/
  173. 48 define_singleton_method(method) do |val|
  174. 48 instance_variable_set(ivar, val)
  175. end
  176. elsif instance_variables.include? ivar_sym
  177. instance_variable_get(ivar)
  178. else
  179. define_singleton_method(method) do
  180. instance_variable_get(ivar)
  181. end
  182. end
  183. 48 send(method, *args, &block)
  184. end
  185. # Do a 'diff' from the current spec (self) and the compare spec
  186. # Returns a hash with attribute as key and an array of the
  187. # attribute values that differed
  188. 2 def diff(compare_spec)
  189. diff_results = Hash.new do |h, k|
  190. h[k] = []
  191. end
  192. # Loop through self's isntance variables first
  193. instance_variables.each do |ivar|
  194. ivar_sym = ivar.to_s.gsub('@', '').to_sym
  195. next if ivar_sym == :notes # temporarily disable until notes diff method written
  196. ivar_str = ivar.to_s.gsub('@', '')
  197. if compare_spec.respond_to? ivar_sym
  198. # Check if the instance variable is a Limit and if so then find
  199. # all instance_variables and diff them as well
  200. if instance_variable_get(ivar).class == Origen::Specs::Spec::Limit
  201. limit_diff_results = diff_limits(instance_variable_get(ivar), compare_spec.instance_variable_get(ivar))
  202. # Extract the limit diff pairs and merge with updated keys with the diff_results hash
  203. limit_diff_results.each do |k, v|
  204. limit_diff_key = "#{ivar_str}_#{k}".to_sym
  205. diff_results[limit_diff_key] = v
  206. end
  207. else
  208. unless instance_variable_get(ivar) == compare_spec.instance_variable_get(ivar)
  209. diff_results[ivar_sym] = [instance_variable_get(ivar), compare_spec.instance_variable_get(ivar)]
  210. Origen.log.debug "Found spec difference for instance variable #{ivar} for #{self} and #{compare_spec}"
  211. end
  212. end
  213. else
  214. # The compare spec doesn't have the current instance variable
  215. # so log a difference
  216. if instance_variable_get(ivar).class == Origen::Specs::Spec::Limit
  217. limit_diff_results = diff_limits(instance_variable_get(ivar), compare_spec.instance_variable_get(ivar))
  218. # Extract the limit diff pairs and merge with updated keys with the diff_results hash
  219. limit_diff_results.each do |k, v|
  220. limit_diff_key = "#{ivar_str}_#{k}".to_sym
  221. diff_results[limit_diff_key] = v
  222. end
  223. else
  224. Origen.log.debug "Instance variable #{ivar} exists for #{self} and does not for #{compare_spec}"
  225. diff_results[ivar_sym] = [instance_variable_get(ivar), '']
  226. end
  227. end
  228. end
  229. # Loop through unique instance variables for compare_spec
  230. diff_results
  231. end
  232. # Monkey patch of hash/array include? method needed because
  233. # Origen::Specs#specs can return a single Spec instance or an Array of Specs
  234. 2 def include?(s)
  235. 1 s == @name ? true : false
  236. end
  237. # Add a specification note
  238. 2 def add_note(id, options = {})
  239. options = {
  240. 1 type: :spec
  241. }.update(options)
  242. # Create the Note instance and add to the notes attribute
  243. 1 @notes[id] = Origen::Specs::Note.new(id, options[:type], options)
  244. end
  245. # Returns a Note object from the notes hash
  246. 2 def notes(id = nil)
  247. 3 return nil if @notes.nil?
  248. 3 @notes.filter(id)
  249. end
  250. # Returns the number of notes as an Integer
  251. 2 def note_count
  252. @notes.size
  253. end
  254. 2 private
  255. 2 def small_name
  256. if @name.to_s[0..@ip_name.to_s.length].include? @ip_name.to_s
  257. ret_name = @name.to_s[@ip_name.to_s.length + 1..-1]
  258. else
  259. ret_name = @name.to_s
  260. end
  261. ret_name = ret_name.partition('-').last if ret_name.include? '-'
  262. ret_name
  263. end
  264. # This assumes the limit objects are Structs
  265. 2 def diff_limits(limit_one, limit_two = nil)
  266. diff_results = Hash.new do |h, k|
  267. h[k] = []
  268. end
  269. # Only need to loop through limit one ivars because the Limit class cannot
  270. # be changed in 3rd party files like the Spec class can be
  271. limit_one.members.each do |m|
  272. if limit_two.respond_to? m
  273. unless limit_one.send(m) == limit_two.send(m)
  274. diff_results[m] = [limit_one.send(m), limit_two.send(m)]
  275. Origen.log.debug "Found limit difference for member #{m} for #{limit_one} and #{limit_two}"
  276. end
  277. else
  278. # Limit two doesn't have the current instance variable or was not provided
  279. # as an argument so log a difference
  280. Origen.log.debug "Member #{m} exists for #{limit_one} and does not for #{limit_two}"
  281. diff_results[m] = [limit_one.send(m), '']
  282. end
  283. end
  284. diff_results
  285. end
  286. 2 def trace_matrix_name_choose
  287. name_set = 0
  288. name_set = 8 unless @name.nil?
  289. name_set += 4 unless @type.nil?
  290. name_set += 2 unless @sub_type.nil?
  291. unless @mode.nil?
  292. unless (@mode.to_s.include? 'local') || (@mode.to_s.include? 'global')
  293. name_set += 1
  294. end
  295. end
  296. name_set
  297. end
  298. end
  299. end
  300. end

lib/origen/specs/spec_features.rb

100.0% lines covered

18 relevant lines. 18 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # Ruby Data Class that contains Creation Information for the IP Block
  4. 2 class Spec_Features
  5. # This is the Id of the Feature that will be referenced
  6. # Future goal is to be able to tie this ID to a specification in a Product Requirements Document
  7. 2 attr_accessor :id
  8. # Feature Type
  9. # Current supported types are
  10. # intro :: Intro Paragraph for the Features Page
  11. # feature :: Main Feature (e.g. Additional peripherals include)
  12. # subfeature :: Sub Feature that will be a sub-bullet to feature. (e.g. Four I2C controllers)
  13. 2 attr_accessor :type
  14. # Feature Reference
  15. # To be used for sub-feature so that they can be linked easily
  16. 2 attr_accessor :feature_ref
  17. # Applicable Devices for this feature. This allows for multiple devices from one piece of silicon
  18. # If this feature is on Part B and Part D, then applicable devices will include Part B and Part D, but no other parts
  19. 2 attr_accessor :applicable_devices
  20. # The actual text of the feature
  21. 2 attr_accessor :text
  22. # Internal comments about this feature. Why was this feature included here? Any changes from the
  23. # Product Requirements Document
  24. 2 attr_accessor :internal_comments
  25. # Intended Audience for this feature. Internal or External?
  26. 2 attr_accessor :audience
  27. # Initialize the Feature to be used
  28. 2 def initialize(id, attrs, applicable_devices, text, internal_comments)
  29. 10 @id = id
  30. 10 @type = attrs[:type]
  31. 10 @feature_ref = attrs[:feature_ref]
  32. 10 @audience = attrs[:audience]
  33. 10 @applicable_devices = applicable_devices
  34. 10 @text = text
  35. 10 @internal_comments = internal_comments
  36. end
  37. end # module Features
  38. end # module Specs
  39. end # module Origen

lib/origen/specs/version_history.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. 2 module Origen
  2. 2 module Specs
  3. # This class is used to store spec exhibit information used to document IP
  4. 2 class Version_History
  5. 2 attr_accessor :label, :date, :author, :changes, :external_changes_internal
  6. 2 def initialize(date, author, changes, label = nil, external_changes_internal = nil)
  7. 8 @date = date
  8. 8 @author = author
  9. 8 @changes = changes
  10. 8 @label = label
  11. 8 @external_changes_internal = external_changes_internal
  12. end
  13. end # class Version History
  14. end # module Specs
  15. end # module Origen

lib/origen/utility/block_args.rb

81.25% lines covered

16 relevant lines. 13 lines covered and 3 lines missed.
    
  1. 1 module Origen
  2. 1 module Utility
  3. # BlockArgs provides a neat way to pass multiple block arguments to a method
  4. # that the method can then used in various ways.
  5. #
  6. # (blocks in Ruby are merely nameless methods you can pass to methods as an argument. Used to pass ruby code to a method basically.)
  7. #
  8. # A single BlockArgs object is an array of these blocks that can be added or
  9. # deleted.
  10. #
  11. # def handle_some_blocks(options={})
  12. #
  13. # blockA = Origen::Utility::BlockArgs.new
  14. # blockB = Origen::Utility::BlockArgs.new
  15. #
  16. # yield blockA, blockB
  17. #
  18. # puts "Handling blocks!"
  19. #
  20. # if options[:block_to_run] == :blockA
  21. # blockA.each do |block|
  22. # block.call
  23. # end
  24. # else
  25. # blockB.each do |block|
  26. # block.call
  27. # end
  28. # end
  29. #
  30. # puts "Done handling blocks!"
  31. #
  32. # end
  33. #
  34. # To then use the above method:
  35. #
  36. # handle_some_blocks(options) do |blockA, blockB|
  37. # blockA.add do
  38. # puts "do task 1"
  39. # end
  40. # blockA.add do
  41. # puts "do task 2"
  42. # end
  43. # blockB.add do
  44. # puts "do task 3"
  45. # end
  46. # end
  47. #
  48. # Many blocks can be added in this case to either the blockA or blockB BlockArg objects.
  49. # The only reason 2 BlockArg objects are used above is that handle_some_blocks wants to use
  50. # different blocks depending on an option argument.
  51. #
  52. # This is a very powerful way to put code specific to one application in a different method in
  53. # different class (e.g. handle_some_blocks) where the code calling it doesn't need to know
  54. # exact implementation details.
  55. #
  56. 1 class BlockArgs
  57. # any Enumerable methods also can be used
  58. # e.g. each_with_index
  59. 1 include Enumerable
  60. # Creates a new BlockArgs object
  61. 1 def initialize
  62. 32 @block_args = []
  63. end
  64. # Adds a block to the BlockArgs object
  65. 1 def add(&block)
  66. 36 @block_args << block
  67. end
  68. # Deletes a block to the BlockArgs object
  69. 1 def delete(&block)
  70. @block_args.delete(block)
  71. end
  72. # required to enumerate objects for Enumerable
  73. # iterator returns each block at a time
  74. 1 def each
  75. 42 @block_args.each do |arg|
  76. 52 yield arg
  77. end
  78. end
  79. # same as each but returns index of each block
  80. # instead of block itself.
  81. 1 def each_index
  82. @block_args.each_index do |i|
  83. yield i
  84. end
  85. end
  86. end
  87. end
  88. end

lib/origen/utility/collector.rb

95.0% lines covered

40 relevant lines. 38 lines covered and 2 lines missed.
    
  1. 1 module Origen
  2. 1 module Utility
  3. 1 class Collector
  4. # Returns the collector's hash. This is the same as calling {#to_h}
  5. 1 attr_reader :_hash_
  6. # Queries the current merge_method.
  7. 1 attr_reader :merge_method
  8. # Need to keep a seperate methods list so we know what's been added by method missing instead of what's
  9. # been added either by the hash or by method missing.
  10. # Only overwriting a value in the block should cause an error. Overriding a value from the hash depends on
  11. # the merge method's setting.
  12. # List of the currently seen method names.
  13. 1 attr_reader :_methods_
  14. # Creates a new Collector object and creates a Hash out of the methods names and values in the given block.
  15. # @see https://origen-sdk.org/origen/guides/misc/utilities/#Collector
  16. # @example Create a collector to transform a block into a Hash
  17. # Origen::Utility::Collector.new { |c| c.my_param 'My Parameter'}.to_h #=> {my_param: 'My Parameter'}
  18. # @yield [self] Passes the collector to the given block.
  19. # @param options [Hash] Customization options.
  20. # @option options [Hash] :hash Input, or starting, values that are set in the output hash prior to calling the given block.
  21. # @option options [Symbol] :merge_method (:keep_hash) Indicates how arguments that exist in both the input hash and in the block should be handled.
  22. # Accpeted values are :keep_hash, :keep_block, :fail.
  23. # @raise [Origen::OrigenError] Raised when an unknown merge method is used.
  24. 1 def initialize(options = {}, &block)
  25. 115 @merge_method = options[:merge_method] || :keep_hash
  26. 115 @fail_on_empty_args = options[:fail_on_empty_args]
  27. 115 unless [:keep_hash, :keep_block, :fail].include?(@merge_method)
  28. 1 fail Origen::OrigenError, "Origen::Utility::Collector cannot merge with method :#{@merge_method} (of class #{@merge_method.class}). Known merge methods are :keep_hash (default), :keep_block, or :fail"
  29. end
  30. 114 @_hash_ = options.key?(:hash) ? options[:hash].clone : {}
  31. 114 @_methods_ = []
  32. 114 if block_given?
  33. 14 yield self
  34. end
  35. end
  36. # Retrieve the collector's hash.
  37. # @deprecated Use Ruby-centric {#to_hash} instead.
  38. # @return [Hash] Hash representation of the collector.
  39. 1 def store
  40. Origen.log.deprecate 'Collector::store method was used. Please use the Ruby-centric Collector::to_h or Collector::to_hash method instead' \
  41. " Called from: #{caller[0]}"
  42. @_hash_
  43. end
  44. # Returns the collector, as a Hash.
  45. # @return [Hash] Hash representation of the collector.
  46. 1 def to_hash
  47. 106 @_hash_
  48. end
  49. 1 alias_method :to_h, :to_hash
  50. # Using the method name, creates a key in the Collector with argument given to the method.
  51. # @see https://origen-sdk.org/origen/guides/misc/utilities/#Collector
  52. # @note If no args are given, the method key is set to <code>nil</code>.
  53. # @raise [ArgumentError] Raised when a method attempts to use both arguments and a block in the same line.
  54. # E.g.: <code>collector.my_param 'my_param' { 'MyParam' }</code>
  55. # @raise [ArgumentError] Raised when a method attempts to use multiple input values.
  56. # E.g.: <code>collector.my_param 'my_param', 'MyParam'</code>
  57. # @raise [Origen::OrigenError] Raised when a method is set more than once. I.e., overriding values are not allowed.
  58. # E.g.: <code>collector.my_param 'my_param'; collector.my_param 'MyParam'</code>
  59. # @raise [Origen::OrigenError] Raised when the input hash and the block attempt to set the same method name and the merge method is set to <code>:fail</code>.
  60. 1 def method_missing(method, *args, &_block)
  61. 44 key = method.to_s.sub('=', '').to_sym
  62. # Check that the arguments are correct
  63. 44 if block_given? && !args.empty?
  64. # raise Origen::OrigenError, "Origen::Utility::Collector detected both the hash and block attempting to set :#{key} (merge_method set to :fail)"
  65. 1 fail ArgumentError, "Origen::Utility::Collector cannot accept both an argument list and block simultaneously for :#{key}. Please use one or the other."
  66. 43 elsif block_given?
  67. 2 val = _block
  68. 41 elsif args.size == 0
  69. # Set any empty argument to nil
  70. 4 val = nil
  71. 37 elsif args.size > 1
  72. 1 fail ArgumentError, "Origen::Utility::Collector does not allow method :#{key} more than 1 argument. Received 3 arguments."
  73. else
  74. 36 val = args.first
  75. end
  76. # Check if we've already added this key via a method
  77. 42 if _methods_.include?(key)
  78. 1 fail Origen::OrigenError, "Origen::Utility::Collector does not allow method :#{key} to be set more than a single time. :#{key} is set to #{_hash_[key]}, tried to set it again to #{val}"
  79. end
  80. # indicate that we've seen this method, and decide whether or not to add the new value
  81. 41 _methods_ << key
  82. # Merge the value (or don't, depending on what is set)
  83. 41 if merge_method == :keep_block || !_hash_.key?(key)
  84. 39 _hash_[key] = val
  85. 2 elsif merge_method == :fail
  86. 1 fail Origen::OrigenError, "Origen::Utility::Collector detected both the hash and block attempting to set :#{key} (merge_method set to :fail)"
  87. end
  88. # store[key] = val if !store.key?(key) || (store.key?(key) && merge_method == :keep_block)
  89. # Return self instead of the key value to allow for one-line collector statements
  90. 40 self
  91. end
  92. end
  93. end
  94. end

lib/origen/utility/diff.rb

86.75% lines covered

83 relevant lines. 72 lines covered and 11 lines missed.
    
  1. 1 module Origen
  2. 1 module Utility
  3. # Diff provides an easy way to diff the contents of two files while optionally
  4. # ignoring any differences in file comments.
  5. #
  6. # differ = Origen::Utility::Diff.new(:ignore_blank_lines => true, :comment_char => "//")
  7. #
  8. # differ.file_a = "#{Origen.root}/my/file1.v"
  9. # differ.file_b = "#{Origen.root}/my/file2.v"
  10. #
  11. # if differ.diffs?
  12. # puts "You've changed something!"
  13. # end
  14. 1 class Diff
  15. # Full path to File A, this attribute must be set before calling any diff actions
  16. 1 attr_accessor :file_a
  17. # Full path to File B, this attribute must be set before calling any diff actions
  18. 1 attr_accessor :file_b
  19. # When true the diff will ignore blank lines, or lines that contain only whitespace
  20. 1 attr_accessor :ignore_blank_lines
  21. # Set this attribute to the comment char used by the given file and comments will
  22. # be ignored by the diff.
  23. # An array of strings can be passed in to mask multiple comment identifiers.
  24. 1 attr_accessor :comment_char
  25. # Create a new diff, attributes can be initialized via the options, or can be
  26. # set later.
  27. 1 def initialize(options = {})
  28. 102 @file_a = options[:file_a]
  29. 102 @file_b = options[:file_b]
  30. 102 @ignore_blank_lines = options[:ignore_blank_lines]
  31. 102 @comment_char = options[:comment_char]
  32. 102 @suspend_string = options[:suspend_string] # permits suspending diff check based on a string
  33. 102 @resume_string = options[:resume_string] # permits resuming diff check based on a string
  34. 102 @suspend_diff = false
  35. 102 @resume_diff = false
  36. end
  37. # Returns true if there are differences between the two files based on the
  38. # current configuration
  39. 1 def diffs?
  40. 102 initialize_counters
  41. 102 result = false
  42. 102 content_a = File.readlines(@file_a)
  43. 102 content_b = File.readlines(@file_b)
  44. 102 changes = false
  45. 102 lines_remaining = true
  46. 102 while lines_remaining
  47. 4407 a = get_next_line_a(content_a) # Get the next vectors
  48. 4407 b = get_next_line_b(content_b)
  49. 4407 if !a && !b # If both patterns finished
  50. 102 lines_remaining = false
  51. 4305 elsif !a || !b # If only 1 pattern finished
  52. lines_remaining = false
  53. changes = true unless @suspend_diff # There are extra vectors in one of the patterns
  54. 4305 elsif a != b # If the vectors don't match
  55. changes = true unless @suspend_diff
  56. end
  57. 4407 if @resume_diff # resume checking diffs for subsequent lines
  58. @suspend_diff = false
  59. @resume_diff = false
  60. end
  61. end
  62. 102 changes
  63. end
  64. 1 private
  65. 1 def set_suspend_diff(line)
  66. 8610 if line.valid_encoding?
  67. 8610 if @suspend_string && !@suspend_diff
  68. 8610 if line =~ /#{@suspend_string}/
  69. @suspend_diff = true
  70. end
  71. elsif @resume_string && @suspend_diff
  72. if line =~ /#{@resume_string}/
  73. @resume_diff = true
  74. end
  75. end
  76. end
  77. end
  78. 1 def get_next_line_b(array)
  79. 4407 @b_ix = next_index(array, @b_ix)
  80. 4407 get_line(array, @b_ix)
  81. end
  82. 1 def get_next_line_a(array)
  83. 4407 @a_ix = next_index(array, @a_ix)
  84. 4407 get_line(array, @a_ix)
  85. end
  86. # Fetches the line from the given array and does some pre-processing
  87. 1 def get_line(array, ix)
  88. 8814 line = array[ix]
  89. 8814 if line
  90. 8610 set_suspend_diff(line)
  91. 8610 if @comment_char
  92. # Screen off any inline comments at the end of line
  93. begin
  94. 8582 [@comment_char].flatten.each do |comchar|
  95. 8582 unless line =~ /^\s*#{comchar}/
  96. 8582 if line =~ /(.*)\s*#{comchar}.*/
  97. 734 return Regexp.last_match[1].strip
  98. else
  99. 7848 return line.strip
  100. end
  101. end
  102. end
  103. # This rescue is a crude way to guard against non-ASCII files that find
  104. # their way in here
  105. rescue
  106. return line
  107. end
  108. else
  109. 28 line.strip
  110. end
  111. end
  112. end
  113. # Find the next line in the given array and return the new index pointer
  114. 1 def next_index(array, ix = nil)
  115. 8814 ix = ix ? ix + 1 : 0
  116. 8814 matched = false
  117. 8814 while !matched && ix < array.size
  118. begin
  119. 17297 comment_matched = false
  120. # Skip comment lines
  121. 17297 if @comment_char
  122. 17265 [@comment_char].flatten.each do |char|
  123. 18453 if array[ix] =~ /^\s*#{char}.*/
  124. 8415 comment_matched = true
  125. end
  126. end
  127. end
  128. # Skip blank lines
  129. 17297 if comment_matched
  130. 8415 ix += 1
  131. 8882 elsif @ignore_blank_lines && array[ix] =~ /^\s*$/
  132. 272 ix += 1
  133. else
  134. 8610 matched = true
  135. end
  136. # This rescue is a crude way to guard against non-ASCII files that find
  137. # there way in here
  138. rescue
  139. matched = true
  140. end
  141. end
  142. 8814 ix
  143. end
  144. 1 def initialize_counters
  145. 102 @a_ix = nil
  146. 102 @b_ix = nil
  147. end
  148. end
  149. end
  150. end

lib/origen/utility/input_capture.rb

10.45% lines covered

67 relevant lines. 7 lines covered and 60 lines missed.
    
  1. 2 require 'readline'
  2. 2 module Origen
  3. 2 module Utility
  4. 2 module InputCapture
  5. # Gets text input from the user
  6. # Supply an optional default value in the event that the user enters nothing
  7. 2 def get_text(options = {})
  8. options = { default: false,
  9. single: false, # Set if only a single line entry is expected
  10. confirm: false,
  11. accept: false, # Supply and array of entries you are willing to accept
  12. case_sensitive: false, # If accept values are supplied they will be treated as case
  13. # in-sensitive by default
  14. wrap: true, # Automatically split long lines
  15. }.merge(options)
  16. if options[:confirm]
  17. puts "Type 'yes' or 'no' to confirm or 'quit' to abort."
  18. elsif options[:accept]
  19. puts "You can enter: #{options[:accept].map { |v| "'#{v}'" }.join(', ')} or 'quit' to abort."
  20. # "
  21. else
  22. puts options[:single] ? "Enter 'quit' to abort." : "Enter a single '.' to finish, or 'quit' to abort."
  23. end
  24. puts '------------------------------------------------------------------------------------------'
  25. text = ''
  26. line = ''
  27. if options[:confirm]
  28. print "(#{options[:default]}): " if options[:default]
  29. else
  30. print "Hit return to accept the default (#{options[:default]}): " if options[:default]
  31. end
  32. while line != '.'
  33. orig_line = Readline.readline('', false).chomp.rstrip
  34. line = orig_line.strip
  35. if (line.empty? || line == '.') && text.empty? && options[:default]
  36. text = options[:default].to_s
  37. line = '.'
  38. elsif line.downcase == 'quit'
  39. exit 0
  40. elsif line == '.'
  41. # Do nothing
  42. else
  43. if options[:wrap]
  44. split_long_line(orig_line) do |short_line|
  45. text << "#{short_line}\n"
  46. end
  47. else
  48. text << orig_line
  49. end
  50. end
  51. confirm = text.strip.downcase if options[:confirm]
  52. text = text.strip if options[:single]
  53. line = '.' if options[:single] || options[:confirm]
  54. end
  55. puts ''
  56. if options[:confirm]
  57. if confirm == 'no' || confirm == 'n'
  58. if options[:confirm] == :return_boolean
  59. return false
  60. else
  61. exit 0
  62. end
  63. end
  64. if confirm == 'yes' || confirm == 'y'
  65. if options[:confirm] == :return_boolean
  66. return true
  67. else
  68. return
  69. end
  70. else
  71. get_text(options)
  72. end
  73. elsif options[:accept]
  74. accept = options[:accept].map do |v|
  75. v = v.to_s
  76. v = v.downcase unless options[:case_sensitive]
  77. v
  78. end
  79. text = text.downcase unless options[:case_sensitive]
  80. text = text.strip
  81. if accept.include?(text)
  82. text
  83. else
  84. get_text(options)
  85. end
  86. else
  87. text
  88. end
  89. end
  90. # Splits a long line into short ones, split by the nearest space
  91. 2 def split_long_line(line)
  92. if line.length <= 90
  93. yield line
  94. else
  95. until line.empty?
  96. if line.length <= 90
  97. yield line
  98. line = ''
  99. else
  100. yield line.slice(0, find_space(line, 90))
  101. line = line.slice(find_space(line, 90), line.length).strip
  102. end
  103. end
  104. end
  105. end
  106. # Find the space closest to but less than max_position, returns max_position if none
  107. # can be found
  108. 2 def find_space(line, max_position)
  109. x = max_position
  110. x -= 1 until line[x] == ' ' || x == 0
  111. x == 0 ? max_position : x
  112. end
  113. end
  114. end
  115. end

lib/origen/value.rb

96.67% lines covered

60 relevant lines. 58 lines covered and 2 lines missed.
    
  1. 2 module Origen
  2. # This class wraps various different class which handle number representation in
  3. # various formats.
  4. #
  5. # The user should never instantiate those directly and should always instantiate an
  6. # Origen::Value instance, thereby ensuring a common API regardless of the internal
  7. # representation and handling of the value
  8. 2 class Value
  9. 2 autoload :HexStrVal, 'origen/value/hex_str_val'
  10. 2 autoload :BinStrVal, 'origen/value/bin_str_val'
  11. # Represents a single bit value of 'X'
  12. 2 class X
  13. 2 def z?
  14. 1 false
  15. end
  16. 2 alias_method :hi_z?, :z?
  17. 2 def x?
  18. 3 true
  19. end
  20. 2 alias_method :undefined?, :x?
  21. 2 def x_or_z?
  22. 1 true
  23. end
  24. 2 alias_method :z_or_x?, :x_or_z?
  25. end
  26. # Represents a single bit value of 'Y'
  27. 2 class Z
  28. 2 def z?
  29. 1 true
  30. end
  31. 2 alias_method :hi_z?, :z?
  32. 2 def x?
  33. 1 false
  34. end
  35. 2 alias_method :undefined?, :x?
  36. 2 def x_or_z?
  37. 1 true
  38. end
  39. 2 alias_method :z_or_x?, :x_or_z?
  40. end
  41. 2 def initialize(val, options = {})
  42. 48 if val.is_a?(Integer)
  43. 1 @val = val
  44. else
  45. 47 val = val.to_s
  46. 47 case val[0].downcase
  47. when 'b'
  48. 22 @val = BinStrVal.new(val, options)
  49. when 'h'
  50. 23 @val = HexStrVal.new(val, options)
  51. when 'd'
  52. 1 @val = val.to_s[1..-1].to_i
  53. else
  54. 1 if val =~ /^[0-9]+$/
  55. 1 @val = val.to_i
  56. else
  57. fail 'Unsupported value syntax'
  58. end
  59. end
  60. end
  61. end
  62. # Returns true if all bits have a numeric value - i.e. no X or Z
  63. 2 def numeric?
  64. 4 val.numeric?
  65. end
  66. 2 def value?
  67. 2 true
  68. end
  69. # Converts to an integer, returns nil if the value contains non-numeric bits
  70. 2 def to_i
  71. 10 val.to_i
  72. end
  73. # Converts to a string, the format of it depends on the underlying value type
  74. 2 def to_s
  75. 8 val.to_s
  76. end
  77. # Returns the size of the value in bits
  78. 2 def size
  79. 6 val.size
  80. end
  81. 2 alias_method :bits, :size
  82. 2 alias_method :number_of_bits, :size
  83. 2 def hex_str_val?
  84. 1 val.is_a?(HexStrVal)
  85. end
  86. 2 alias_method :hex_str_value?, :hex_str_val?
  87. 2 def bin_str_val?
  88. 1 val.is_a?(BinStrVal)
  89. end
  90. 2 alias_method :bin_str_value?, :bin_str_val?
  91. 2 def [](index)
  92. 16 if index.is_a?(Range)
  93. fail 'Currently, only single bit extraction from a Value object is supported'
  94. end
  95. 16 val[index]
  96. end
  97. 2 private
  98. 2 def val
  99. 46 @val
  100. end
  101. end
  102. end

lib/origen/value/bin_str_val.rb

97.22% lines covered

36 relevant lines. 35 lines covered and 1 lines missed.
    
  1. 1 module Origen
  2. 1 class Value
  3. # Handles a value represented by a string of bin character(s) [0, 1, x, z]
  4. #
  5. # Capital X/Z will be accepted when defining the value, but they will be converted
  6. # to lower case
  7. 1 class BinStrVal
  8. 1 attr_reader :val, :size
  9. 1 def initialize(value, options)
  10. 22 @val = clean(value)
  11. 21 if options[:size]
  12. 7 @size = options[:size]
  13. # Trim any bits that are out of range...
  14. 7 @val = val.split(//).last(size).join
  15. else
  16. 14 @size = val.size
  17. end
  18. end
  19. 1 def numeric?
  20. 13 !!(val =~ /^[01]+$/)
  21. end
  22. 1 def to_i
  23. 5 if numeric?
  24. 4 val.to_i(2) & size.bit_mask
  25. end
  26. end
  27. 1 def to_s
  28. 4 "b#{val}"
  29. end
  30. # Returns the value of the given bit.
  31. # Return nil if out of range, otherwise 0, 1 or an X or Z object
  32. 1 def [](index)
  33. 8 unless index > (size - 1)
  34. 6 if numeric?
  35. 2 to_i[index]
  36. else
  37. 4 char = val[val.size - 1 - index]
  38. 4 if char == 'x'
  39. 1 X.new
  40. 3 elsif char == 'z'
  41. Z.new
  42. else
  43. 3 char.to_i
  44. end
  45. end
  46. end
  47. end
  48. 1 private
  49. 1 def clean(val)
  50. 22 val = val.to_s.strip.to_s[1..-1]
  51. 22 if valid?(val)
  52. 21 val.gsub('_', '').downcase
  53. end
  54. end
  55. 1 def valid?(val)
  56. 22 if val =~ /^[01_xXzZ]+$/
  57. 21 true
  58. else
  59. 1 fail Origen::BinStrValError, "Binary string values can only contain: 0, 1, _, x, X, z, Z, this is invalid: #{val}"
  60. end
  61. end
  62. end
  63. end
  64. end

lib/origen/value/hex_str_val.rb

97.96% lines covered

49 relevant lines. 48 lines covered and 1 lines missed.
    
  1. 1 module Origen
  2. 1 class Value
  3. # Handles a value represented by a string of hex character(s) [0-9, a-f, x, X, z, Z]
  4. #
  5. # This is
  6. #
  7. # * x when all the bits in a nibble are x
  8. # * X when some of the bits in a nibble are x, though the exact bit-level values are not known
  9. # * z when all the bits in a nibble are z
  10. # * Z when some of the bits in a nibble are z, though the exact bit-level values are not known
  11. #
  12. # Capital hex numbers will be accepted when defining the value, but they will be converted
  13. # to lower case
  14. 1 class HexStrVal
  15. 1 attr_reader :val, :size
  16. 1 def initialize(value, options)
  17. 23 @val = clean(value)
  18. 22 if options[:size]
  19. 8 @size = options[:size]
  20. # Trim any nibbles that are out of range...
  21. 8 @val = val.split(//).last(size_in_nibbles).join
  22. else
  23. 14 @size = (val.size * 4)
  24. end
  25. end
  26. 1 def numeric?
  27. 14 !!(val =~ /^[0-9a-f]+$/)
  28. end
  29. 1 def to_i
  30. 6 if numeric?
  31. 5 val.to_i(16) & size.bit_mask
  32. end
  33. end
  34. 1 def to_s
  35. 4 "h#{val}"
  36. end
  37. # Returns the value of the given bit.
  38. # Return nil if out of range, otherwise 0, 1 or an X or Z object
  39. 1 def [](index)
  40. 8 unless index > (size - 1)
  41. 6 if numeric?
  42. 2 to_i[index]
  43. else
  44. # Get the nibble in question and re-align the index, if the bit falls in a numeric
  45. # part of the string we can still resolve to an integer
  46. 4 nibble = nibble_of(index)
  47. 4 nibble = val[val.size - 1 - nibble]
  48. 4 if nibble.downcase == 'x'
  49. 1 X.new
  50. 3 elsif nibble.downcase == 'z'
  51. Z.new
  52. else
  53. 3 nibble.to_i[index % 4]
  54. end
  55. end
  56. end
  57. end
  58. 1 private
  59. 1 def nibble_of(bit_number)
  60. 4 bit_number / 4
  61. end
  62. # Rounds up to the nearest whole nibble
  63. 1 def size_in_nibbles
  64. 8 adder = size % 4 == 0 ? 0 : 1
  65. 8 (size / 4) + adder
  66. end
  67. 1 def clean(val)
  68. 23 val = val.to_s.strip.to_s[1..-1]
  69. 23 if valid?(val)
  70. 22 if val =~ /[A-F]/
  71. 10 val = val.gsub('A', 'a')
  72. 10 val = val.gsub('B', 'b')
  73. 10 val = val.gsub('C', 'c')
  74. 10 val = val.gsub('D', 'd')
  75. 10 val = val.gsub('E', 'e')
  76. 10 val = val.gsub('F', 'f')
  77. end
  78. 22 val.gsub('_', '')
  79. end
  80. end
  81. 1 def valid?(val)
  82. 23 if val =~ /^[0-9a-fA-F_xXzZ]+$/
  83. 22 true
  84. else
  85. 1 fail Origen::HexStrValError, "Hex string values can only contain: 0-9, a-f, A-F, _, x, X, z, Z, this is invalid: #{val}"
  86. end
  87. end
  88. end
  89. end
  90. end